1. 程式人生 > >資料層的多租戶淺談(SAAS多租戶資料庫設計)

資料層的多租戶淺談(SAAS多租戶資料庫設計)

在上一篇“淺析多租戶在 Java 平臺和某些 PaaS 上的實現”中我們談到了應用層面的多租戶架構,涉及到 PaaS、JVM、OS 等,與之相應的是資料層也有多租戶的支援。

資料層的多租戶綜述

多租戶(Multi Tenancy/Tenant)是一種軟體架構,其定義是:

在一臺伺服器上執行單個應用例項,它為多個租戶提供服務。

在SaaS實施過程中,有一個顯著的考量點,就是如何對應用資料進行設計,以支援多租戶,而這種設計的思路,是要在資料的共享、安全隔離和效能間取得平衡。

傳 統的應用,僅僅服務於單個租戶,資料庫多部署在企業內部網路環境,對於資料擁有者來說,這些資料是自己“私有”的,它符合自己所定義的全部安全標準。而在 雲端計算時代,隨著應用本身被放到雲端,導致資料層也經常被公開化,但租戶對資料安全性的要求,並不因之下降。同時,多租戶應用在租戶數量增多的情況下,會 比單租戶應用面臨更多的效能壓力。本文即對這個主題進行探討:多租戶在資料層的框架如何在共享、安全與效能間進行取捨,同時瞭解一下市面上一些常見的資料 廠商怎樣實現這部分內容。

常見的三種模式

在 MSDN 的這篇文章 Multi-Tenant Data Architecture 中,系統的總結了資料層的三種多租戶架構

  1. 獨立資料庫
  2. 共享資料庫、獨立 Schema
  3. 共享資料庫、共享 Schema、共享資料表

獨立資料庫是一個租戶獨享一個數據庫例項,它提供了最強的分離度,租戶的資料彼此物理不可見,備份與恢復都很靈活;共享資料庫、獨立 Schema 將每個租戶關聯到同一個資料庫的不同 Schema,租戶間資料彼此邏輯不可見,上層應用程式的實現和獨立資料庫一樣簡單,但備份恢復稍顯複雜; 最後一種模式則是租戶資料在資料表級別實現共享,它提供了最低的成本,但引入了額外的程式設計複雜性(程式的資料訪問需要用 tenantId 來區分不同租戶),備份與恢復也更復雜。這三種模式的特點可以用一張圖來概括:

圖 1. 三種部署模式的異同

 

上圖所總結的是一般性的結論,而在常規場景下需要綜合考慮才能決定那種方式是合適的。例如,在佔用成本上,認為獨立資料庫會高,共享模式較低。但如果考慮到大租戶潛在的資料擴充套件需求,有時也許會有相反的成本耗用結論。

而多租戶採用的選擇,主要是成本原因,對於多數場景而言,共享度越高,軟硬體資源的利用效率更好,成本也更低。但同時也要解決好租戶資源共享和隔離帶來的安全與效能、擴充套件性等問題。畢竟,也有客戶無法滿意於將資料與其他租戶放在共享資源中。

目前市面上各類資料廠商在多租戶的支援上,大抵都是遵循上文所述的這幾類模式,或者混合了幾種策略,這部分內容將在下面介紹。

JPA Provider

JSR 338 定義了 JPA 規範 2.1,但如我們已經瞭解到的,Oracle 把多租戶的多數特性推遲到了 Java EE 8 中。儘管這些曾經在 JavaOne 大會中有過演示,但無論是在 JPA 2.0(JSR 317)還是 2.1 規範中,都依然沒有明文提及多租戶。不過這並不妨礙一些 JPA provider 在這部分領域的實現,Hibernate 和 EclipseLink 已提供了全部或部分的多租戶資料層的解決方案。

Hibernate 是當今最為流行的開源的物件關係對映(ORM)實現,並能很好地和 Spring 等框架整合,目前 Hibernate 支援多租戶的獨立資料庫和獨立 Schema 模式。EclipseLink 也是企業級資料持久層JPA標準的參考實現,對最新 JPA2.1 完整支援,在目前 JPA 標準尚未引入多租戶概念之際,已對多租戶支援完好,其前身是誕生已久、功能豐富的物件關係對映工具 Oracle TopLink。因此本文采用 Hibernate 和 EclipseLink 對多租戶資料層進行分析。

Hibernate

Hibernate 是一個開放原始碼的物件/關係對映框架和查詢服務。它對 JDBC 進行了輕量級的物件封裝,負責從 Java 類對映到資料庫表,並從 Java 資料型別對映到 SQL 資料型別。在 4.0 版本 Hibenate 開始支援多租戶架構——對不同租戶使用獨立資料庫或獨立 Sechma,並計劃在 5.0 中支援共享資料表模式。

在 Hibernate 4.0 中的多租戶模式有三種,通過 hibernate.multiTenancy 屬性有下面幾種配置:

  • NONE:非多租戶,為預設值。
  • SCHEMA:一個租戶一個 Schema。
  • DATABASE:一個租戶一個 database。
  • DISCRIMINATOR:租戶共享資料表。計劃在 Hibernate5 中實現。

模式1:獨立資料庫

如果是獨立資料庫,每個租戶的資料儲存在物理上獨立的資料庫例項。JDBC 連線將指向具體的每個資料庫,一個租戶對應一個數據庫例項。在 Hibernate 中,這種模式可以通過實現 MultiTenantConnectionProvider 介面或繼承 AbstractMultiTenantConnectionProvider 類等方式來實現。三種模式中它的共享性最低,因此本文重點討論以下兩種模式。

模式 2:共享資料庫,獨立 Schema

對於共享資料庫,獨立 Schema。所有的租戶共享一個數據庫例項,但是他們擁有獨立的 Schema 或 Catalog,本文將以多租戶酒店管理系統為案例說明 Hibernate 對多租戶的支援和用使用方法。

圖 2. guest 表結構

 

這是酒店客戶資訊表,我們僅以此表對這種模式進行說明,使用相同的表結構在 MySQL 中建立 DATABASE hotel_1 和 hotel_2。基於 Schema 的多租戶模式,需要在 Hibernate 配置檔案 Hibernate.cfg.xml 中設定 hibernate.multiTenancy 等相關屬性。

清單 1. 配置檔案 Hibernate.cfg.xml
<session-factory>
<property name="connection.url">
    jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&amp;characterEncoding=utf8
</property>
<property name="connection.username">root</property>
<property name="connection.password"></property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>
<property name="hibernate.connection.autocommit">false</property>
<property name="hibernate.cache.use_second_level_cache">false</property>
<property name="show_sql">false</property>

<property name="hibernate.multiTenancy">SCHEMA</property>
<property name="hibernate.tenant_identifier_resolver">
     hotel.dao.hibernate.TenantIdResolver
</property>
<property name="hibernate.multi_tenant_connection_provider">
     hotel.dao.hibernate.SchemaBasedMultiTenantConnectionProvider
</property>

<mapping class="hotel.model.Guest" />
</session-factory>

<hibernate.tenant_identifier_resolver> 屬性規定了一個合約,以使 Hibernate 能夠解析出應用當前的 tenantId,該類必須實現 CurrentTenantIdentifierResolver 介面,通常我們可以從登入資訊中獲得 tenatId。

清單 2. 獲取當前 tenantId
public class TenantIdResolver implements CurrentTenantIdentifierResolver {
	public String resolveCurrentTenantIdentifier() {
		return Login.getTenantId();
	}
}

< hibernate.multi_tenant_connection_provider> 屬性指定了 ConnectionProvider,即 Hibernate 需要知道如何以租戶特有的方式獲取資料連線,SchemaBasedMultiTenantConnectionProvider 類實現了MultiTenantConnectionProvider 介面,根據 tenantIdentifier 獲得相應的連線。在實際應用中,可結合使用 JNDI DataSource 技術獲取連線以提高效能。

清單 3. 以租戶特有的方式獲取資料庫連線
public class SchemaBasedMultiTenantConnectionProvider 
  implements MultiTenantConnectionProvider, Stoppable,
		Configurable, ServiceRegistryAwareService {

	private final DriverManagerConnectionProviderImpl connectionProvider 
         = new DriverManagerConnectionProviderImpl();
	@Override
	public Connection getConnection(String tenantIdentifier) throws SQLException {
		final Connection connection = connectionProvider.getConnection();
		connection.createStatement().execute("USE " + tenantIdentifier);
		
		return connection;
	}

	@Override
	public void releaseConnection(String tenantIdentifier, Connection connection)
 throws SQLException {
		connection.createStatement().execute("USE test");		
		connectionProvider.closeConnection(connection);
	}
	……	
}

與表 guest 對應的 POJO 類 Guest,其中主要是一些 getter 和 setter方法。

清單 4. POJO 類 Guest
@Table(name = "guest")
public class Guest {

	private Integer id;
	private String name;
	private String telephone;
	private String address;
	private String email;

	//getters and setters
		……	
}

我們使用 ServiceSchemaBasedMain.java 來進行測試,並假設了一些資料以方便演示,如當有不同租戶的管理員登入後分別進行新增客戶的操作。

清單 5. 測試類 ServiceSchemaBasedMain
public class ServiceSchemaBasedMain {

	public static void main(String[] args) {
		Session session = null;
		Guest guest =null;
		List<Guest> list = null;
		Transaction tx = null;

		System.out.println("======== 租戶 hotel_1 ========");
		Login.setTenantId("hotel_1");
		session = sessionFactory.openSession();
		tx = session.beginTransaction();
		guest = new Guest();
		guest.setName("張三");
		guest.setTelephone("56785678");
		guest.setAddress("上海市張揚路88號");
		guest.setEmail("[email protected]");
		session.saveOrUpdate(guest);
		list = session.createCriteria(Guest.class).list();
		for (Guest gue : list) {
			System.out.println(gue.toString());
		}
		tx.commit();
		session.close();
		

		System.out.println("======== 租戶 hotel_2 ========");
		Login.setTenantId("hotel_2");
		session = sessionFactory.openSession();
		tx = session.beginTransaction();
		guest = new Guest();
		guest.setName("李四");
		guest.setTelephone("23452345");
		guest.setAddress("上海市南京路100號");
		guest.setEmail("[email protected]");
		session.saveOrUpdate(guest);
		list = session.createCriteria(Guest.class).list();
		for (Guest gue : list) {
			System.out.println(gue.toString());
		}
		tx.commit();
		session.close();
	}
}
清單 6. 執行程式 ServiceSchemaBasedMain 的輸出
======== 租戶 hotel_1 ========
Guest [id=1, name=Victor, telephone=56008888, address=上海科苑路399號, [email protected]]
Guest [id=2, name=Jacky, telephone=66668822, address=上海金科路28號, [email protected]]
Guest [id=3, name=張三, telephone=56785678, address=上海市張揚路88號, [email protected]]
======== 租戶 hotel_2 ========
Guest [id=1, name=Anton, telephone=33355566, address=上海南京路8號, [email protected]]
Guest [id=2, name=Gus, telephone=33355566, address=北京大道3號, [email protected]]
Guest [id=3, name=李四, telephone=23452345, address=上海市南京路100號, [email protected]]

模式3:共享資料庫、共享 Schema、共享資料表

在這種情況下,所有租戶共享資料表存放資料,不同租戶的資料通過 tenant_id 鑑別器來區分。但目前的 Hibernate 4 還不支援這個多租戶鑑別器策略,要在 5.0 才支援。但我們是否有可選的替代方案呢?答案是使用 Hibernate Filter.

為了區分多個租戶,我在 Schema 的每個資料表需要新增一個欄位 tenant_id 以判定資料是屬於哪個租戶的。

圖 3. 共享 Schema、共享資料表案例 E-R 圖

 

根據上圖在 MySQL 中建立 DATABASE hotel。

我們在 OR-Mapping 配置檔案中使用了 Filter,以便在進行資料查詢時,會根據 tenant_id 自動查詢出該租戶所擁有的資料。

清單 7. 物件關係對映檔案 Room.hbm.xml

點選檢視程式碼清單

接下來我們在 HibernateUtil 類中通過 ThreadLocal 存放和獲取 Hibernate Session,並將使用者登入資訊中的 tenantId 設定為 tenantFilterParam 的引數值。

清單 8. 獲取 Hibernate Session 的工具類 HibernateUtil

點選檢視程式碼清單

不過 Filter 只是有助於我們讀取資料時顯示地忽略掉 tenantId,但在進行資料插入的時候,我們還是不得不顯式設定相應 tenantId 才能進行持久化。這種狀況只能在 Hibernate5 版本中得到根本改變。

清單 9. 執行程式 HotelServiceMain 輸出

點選檢視程式碼清單

多租戶下的 Hibernate 快取

基於獨立 Schema 模式的多租戶實現,其資料表無需額外的 tenant_id。通過 ConnectionProvider 來取得所需的 JDBC 連線,對其來說一級快取(Session 級別的快取)是安全的可用的,一級快取對事物級別的資料進行快取,一旦事物結束,快取也即失效。但是該模式下的二級快取是不安全的,因為多個 Schema 的資料庫的主鍵可能會是同一個值,這樣就使得 Hibernate 無法正常使用二級快取來存放物件。例如:在 hotel_1 的 guest 表中有個 id 為 1 的資料,同時在 hotel_2 的 guest 表中也有一個 id 為 1 的資料。通常我會根據 id 來覆蓋類的 hashCode() 方法,這樣如果使用二級快取,就無法區別 hotel_1 的 guest 和 hote_2 的 guest。

在共享資料表的模式下的快取, 可以同時使用 Hibernate的一級快取和二級快取, 因為在共享的資料表中,主鍵是唯一的,資料表中的每條記錄屬於對應的租戶,在二級快取中的物件也具有唯一性。Hibernate 分別為 EhCache、OSCache、SwarmCache 和 JBossCache 等快取外掛提供了內建的 CacheProvider 實現,讀者可以根據需要選擇合理的快取,修改 Hibernate 配置檔案設定並啟用它,以提高多租戶應用的效能。

EclipseLink

EclipseLink 是 Eclipse 基金會管理下的開源持久層服務專案,為 Java 開發人員與各種資料服務(比如:資料庫、web services、物件XML對映(OXM)、企業資訊系統(EIS)等)互動提供了一個可擴充套件框架,目前支援的持久層標準中包括:

  • Java Persistence API (JPA)
  • Java Architecture for XML Binding (JAXB)
  • Java Connector Architecture (JCA)
  • Service Data Objects (SDO)

EclipseLink 前身是 Oracle TopLink, 2007年 Oracle 將後者絕大部分捐獻給了 Eclipse 基金會,次年 EclipseLink 被 Sun 挑選成為 JPA 2.0 的參考實現。

注: 目前 EclipseLink2.5 完全支援 2013 年釋出的 JPA2.1(JSR 338) 。

在完整實現 JPA 標準之外,針對 SaaS 環境,在多租戶的隔離方面 EclipseLink 提供了很好的支援以及靈活地解決方案。

應用程式隔離

  • 隔離的容器/應用伺服器
  • 共享容器/應用伺服器的應用程式隔離
  • 同一應用程式內的共享快取但隔離的 entity manager factory
  • 共享的 entity manager factory 但每隔離的 entity manager

資料隔離

  • 隔離的資料庫
  • 隔離的Schema/表空間
  • 隔離的表
  • 共享表但隔離的行
  • 查詢過濾
  • Oracle Virtual Private Database (VPD)

對於多租戶資料來源隔離主要有以下方案

  • Single-Table Multi-tenancy,依靠租戶區分列(tenant discriminator columns)來隔離表的行,實現多租戶共享表。
  • Table-Per-Tenant Multi-tenancy,依靠表的租戶區分(table tenant discriminator)來隔離表,實現一租戶一個表,大體類似於上文的共享資料庫獨立Schema模式。
  • Virtual Private Database(VPD ) Multi-tenancy,依靠 Oracle VPD 自身的安全訪問策略(基於動態SQL where子句特性),實現多租戶共享表。

本節重點介紹多租戶在 EclipseLink 中的共享資料表和一租戶一個表的實現方法,並也以酒店多租戶應用的例子展現共享資料表方案的具體實踐。

EclipseLink Annotation @Multitenant

與 @Entity 或 @MappedSuperclass 一起使用,表明它們在一個應用程式中被多租戶共享, 如清單 10。

清單10. @Multitenant
@Entity
@Table(name="room")
@Multitenant
...
publicclass Room {
}
表 1. Multitenant 包含兩個屬性
Annotation 屬性 描述 預設值
boolean includeCriteria 是否將租戶限定應用到 select、update、delete 操作上 true
MultitenantType value 多租戶策略,SINGLE_TABLE, TABLE_PER_TENANT, VPD. SINGLE_TABLE

共享資料表(SINGLE_TABLE)

  1. Metadata配置

依靠租戶區分列修飾符 @TenantDiscriminatorColumn 實現。

清單11. @TenantDiscriminatorColumn
@Entity
@Table(name="hotel_guest")
@Multitenant(SINGLE_TABLE)
@TenantDiscriminatorColumn(name="tenant_id", contextProperty="tenant.id")
publicclass HotelGuest {
}

或者在EclipseLink描述檔案orm.xml定義物件與表對映時進行限制,兩者是等價的。

清單12. orm.xml
<entity class="mtsample.hotel.model.HotelGuest">
  <multitenant>
    <tenant-discriminator-column name="tenant_id" context-property="tenant.id"/>
  </multitenant>
  <table name="HotelGuest"/>
  ...
</entity>
  1. 屬性配置

租戶區分列定義好後,在執行時環境需要配置具體屬性值,以確定當前操作環境所屬的租戶。

三種方式的屬性配置,按優先生效順序排序如下

  1. EntityManager(EM)
  2. EntityManagerFactory(EMF)
  3. Application context (when in a Java EE container)

例如 EntityManagerFactory 可以間接通過在 persistence.xml 中配置持久化單元(Persistence Unit)或直接傳屬性引數給初始化時 EntityManagerFactory。

清單 13. 配置 persistence.xml
<persistence-unit name="multi-tenant">
  ...
  <properties>
    <property name="tenant_id" value="開發部"/>
    ...
  </properties>
</persistence-unit>

或者

清單 14. 初始化 EntityManagerFactory

點選檢視程式碼清單

按共享粒度可以作如下區分,

  • EntityManagerFactory 級別

使用者需要通過 eclipselink.session-name 提供獨立的會話名,確保每個租戶佔有獨立的會話和快取。

清單 15. 為 EntityManagerFactory 配置會話名

點選檢視程式碼清單

  • 共享的 EntityManagerFactory 級別

EntityManagerFactory 的預設模式, 此級別預設配置為獨立二級快取(L2 cache), 即每個 mutlitenant 實體快取設定為 ISOLATED,使用者也可設定 eclipselink.multitenant.tenants-share-cache 屬性為真以共享,此時多租戶 Entity 快取設定為 PROTECTED。

這種級別下,一個活動的 EntityManager 不能更換 tenantId。

  • EntityManager 級別

這種級別下,共享 session,共享 L2 cache, 使用者需要自己設定快取策略,以設定哪些租戶資訊是不能在二級快取共享的。

清單 16. 設定快取

點選檢視程式碼清單

同樣,一個活動的EntityManager不能更換tenant ID。

幾點說明:

  • 每個表的區分列可以有任意多個,使用修飾符 TenantDiscriminatorColumns。
清單 17. 多個分割槽列
@TenantDiscriminatorColumns({
@TenantDiscriminatorColumn(name="tenant_id", contextProperty="tenant.id"),
@TenantDiscriminatorColumn(name = "guest_id", contextProperty="guest.id")
})
  • 租戶區分列的名字和對應的上下文屬性名可以取任意值,由應用程式開發者設定。
  • 生成的 Schema 可以也可以不包含租戶區分列,如 tenant_id 或 guest_id。
  • 租戶區分列可以對映到實體物件也可以不。

注意:當對映的時候,實體物件相應的屬性必須標記為只讀(insertable=false, updatable=false),這種限制使得區分列不能作為實體表的 identifier。

  • TenantDiscriminatorColumn被以下 EntityManager 的操作和查詢支援:

persist,find,refresh,named queries,update all,delete all 。

一租戶一表(TABLE_PER_TENANT )

這種多租戶型別使每個租戶的資料可以佔據專屬它自己的一個或多個表,多租戶間的這些表可以共享相同 Schema 也可使用不同的,前者使用字首(prefix)或字尾(suffix)命名模式的表的租戶區分符,後者使用租戶專屬的 Schema 名來定義表的租戶區分符。

  1. Metadata配置

依靠資料表的租戶區分修飾符 @TenantTableDiscriminator 實現

清單 18.
@Entity
@Table(name=“CAR”)
@Multitenant(TABLE_PER_TENANT)
@TenantTableDiscriminator(type=SCHEMA, contextProperty="eclipselink-tenant.id")
public class Car{
}

清單 19.
<entity class="Car">
  <multitenant type="TABLE_PER_TENANT">
    <tenant-table-discriminator type="SCHEMA" context-property="eclipselink.tenant-id"/>
  </multitenant>
  <table name="CAR">
</entity>

如前所述,TenantTableDiscriminatorType有 3 種類型:SCHEMA、SUFFIX 和 PREFIX。

  1. 屬性配置

與另外兩種多租戶型別一樣,預設情況下,多租戶共享EMF,如不想共享 EMF,可以通過配置 PersistenceUnitProperties.MULTITENANT_SHARED_EMF 以及 PersistenceUnitProperties.SESSION_NAME 實現。

清單 20.

點選檢視程式碼清單

或在 persistence.xml 配置屬性。

酒店多租戶應用例項(EclipseLink 共享(單)表)

資料庫 Schema 和測試資料與上文 Hibernate 實現相同,關於物件關係對映(OR mapping)的配置均採用 JPA 和 EclipseLink 定義的 Java Annotation 描述。

關於幾個基本操作

  1. 新增一個物件例項, 利用EntityManager.persist()
清單 21. 新增
public <T> void save(T t) {
		em.persist(t);
	}
	public <T> void saveBulk(List<T> bulk) {
		for(T t:bulk){
			em.persist(t);
		}
	}
  1. 更新一個物件例項, 利用EntityManager.merge()
清單 22. 更新
public <T> void update(T t){
		em.merge(t);
	}
  1. 查詢, 利用EntityManager的NamedQuery,
清單 23. 多條件多結果查詢

點選檢視程式碼清單

若用 JPQL 實現則示例如下:

清單 24. JPQL NamedQuery 定義

點選檢視程式碼清單

部分測試資料如下(MySQL):

hotel_admin

 

hotel_guest

 

room

 

執行附件 MT_Test_Hotels.zip 中的測試程式碼(請參照 readme)來看看多租戶的一些典型場景。

清單 25. 執行測試程式碼

點選檢視程式碼清單

能得到輸出片段如下:

清單 26. 輸出

點選檢視程式碼清單

通過共享表的測試資料以及執行結果可以看到,對於多個不同的租戶(hotel_admin),在新增、查詢、更新操作沒有顯示宣告租戶標識的情況下,EntityManager 可以根據自身的租戶屬性配置

實現租戶分離。在本例項,EntityManager 初始化時利用到 hotel_admin 登入後的會話上下文進行租戶判斷,這裡不再贅述。

注:上文中提及的全部原始碼都可以在附件中找到。

其它方面的考慮

資料備份

獨立資料庫和獨立Sechma的模式,為每個租戶備份資料比較容易,因為他們存放在不同的資料表中,只需對整個資料庫或整個Schema進行備份。

在 共享資料表的模式下,可以將所有租戶的資料一起備份,但是若要為某一個租戶或按租戶分開進行資料備份,就會比較麻煩。通常需要另外寫sql指令碼根據 tenant_id來取得對應的資料然後再備份,但是要按租戶來匯入的話依然比較麻煩,所以必要時還是需要備份所有併為以後匯入方便。

效能

獨立資料庫:效能高,但價格也高,需要佔用資源多,不能共享,價效比低。

共享資料庫,獨立 Schema:效能中等,但價格合適,部分共享,價效比中等。

共享資料庫,共享 Schema,共享資料表:效能中等(可利用 Cache 可以提高效能),但價格便宜,完全共享,價效比高。如果在某些表中有大量的資料,可能會對所有租戶產生效能影響。

對於共享資料庫的情況下,如果因為太多的終端使用者同時訪問資料庫而導致應用程式效能問題,可以考慮資料表分割槽等資料庫端的優化方案。

經濟考慮

為了支援多租戶應用,共享模式的應用程式往往比使用獨立資料庫模式的應用程式相對複雜,因為開發一個共享的架構,導致在應用設計上得花較大的努力,因而初始成本會較高。然而,共享模式的應用在運營成本上往往要低一些,每個租戶所花的費用也會比較低。

結束語

多租戶資料層方案的選擇是一個綜合的考量過程,包括成本、資料隔離與保護、維護、容災、效能等。但無論怎樣選擇,OR-Mapping 框架對多租戶的支援將極大的解放開發人員的工作,從而可以更多專注於應用邏輯。最後我們以一個 Hibernate 和 EclipseLink 的比較來結束本文。

表 2. Hibernate 與 EclipeLink 對多租戶支援的比較
  Hibernate EclipseLink
獨立資料庫 支援,通過實現 MultiTenantConnectionProvider 介面以連線獨立的資料庫 支援,為每個租戶配置獨立的 EntityManagerFactory
共享資料庫,獨立 Schema 支援,通過實現 MultiTenantConnectionProvider 介面以切換 Schema 支援,使用 TABLE_PER_TENANT MultitenantType 以及 SCHEMA TenantTableDiscriminatorType
共享資料庫,共享 Schema,共享資料表