数据库schema中Schema和Database有什么区别

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
您的访问请求被拒绝 403 Forbidden - ITeye技术社区
您的访问请求被拒绝
亲爱的会员,您的IP地址所在网段被ITeye拒绝服务,这可能是以下两种情况导致:
一、您所在的网段内有网络爬虫大量抓取ITeye网页,为保证其他人流畅的访问ITeye,该网段被ITeye拒绝
二、您通过某个代理服务器访问ITeye网站,该代理服务器被网络爬虫利用,大量抓取ITeye网页
请您点击按钮解除封锁&IBM Bluemix
点击按钮,开始云上的开发!
developerWorks 社区
在单租户应用向多租户应用的转型中,数据如何隔离,既满足数据服务的共享,又保证数据的安全性,同时性能也在合理的考量中,是一个共同的课题。同时,我们也关注像
Hibernate、EclipseLink 等数据层的解决方案又是如何具体实现多租户的。
, 软件工程师,
刘盛彬,PMP。拥有多年的企业应用、电子商务系统开发和项目管理经验,现就职于 IBM CSTL 部门。
, 软件工程师,
任乐天,有多年系统管理软件和计算机安全开发经验,目前从事 Open Stack Enterprise 相关项目开发工作。
, 软件工程师,
陈争云,长期专注于 J2EE 开发与设计,有多年企业级产品和大型电商平台的架构与开发经验,现就职于 IBM CSTL 部门。与人合作译有 Mike Cohn 大作《 Scrum 敏捷软件开发》。
在上一篇中我们谈到了应用层面的多租户架构,涉及到 PaaS、JVM、OS
等,与之相应的是数据层也有多租户的支持。数据层的多租户综述多租户(Multi Tenancy/Tenant)是一种软件架构,其定义是:在一台服务器上运行单个应用实例,它为多个租户提供服务。在SaaS实施过程中,有一个显著的考量点,就是如何对应用数据进行设计,以支持多租户,而这种设计的思路,是要在数据的共享、安全隔离和性能间取得平衡。传统的应用,仅仅服务于单个租户,数据库多部署在企业内部网络环境,对于数据拥有者来说,这些数据是自己“私有”的,它符合自己所定义的全部安全标准。而在云计算时代,随着应用本身被放到云端,导致数据层也经常被公开化,但租户对数据安全性的要求,并不因之下降。同时,多租户应用在租户数量增多的情况下,会比单租户应用面临更多的性能压力。本文即对这个主题进行探讨:多租户在数据层的框架如何在共享、安全与性能间进行取舍,同时了解一下市面上一些常见的数据厂商怎样实现这部分内容。常见的三种模式在 MSDN 的这篇文章
中,系统的总结了数据层的三种多租户架构: 独立数据库 共享数据库、独立 Schema 共享数据库、共享 Schema、共享数据表独立数据库是一个租户独享一个数据库实例,它提供了最强的分离度,租户的数据彼此物理不可见,备份与恢复都很灵活;共享数据库、独立 Schema
将每个租户关联到同一个数据库的不同 Schema,租户间数据彼此逻辑不可见,上层应用程序的实现和独立数据库一样简单,但备份恢复稍显复杂;
最后一种模式则是租户数据在数据表级别实现共享,它提供了最低的成本,但引入了额外的编程复杂性(程序的数据访问需要用 tenantId
来区分不同租户),备份与恢复也更复杂。这三种模式的特点可以用一张图来概括:图 1. 三种部署模式的异同上图所总结的是一般性的结论,而在常规场景下需要综合考虑才能决定那种方式是合适的。例如,在占用成本上,认为独立数据库会高,共享模式较低。但如果考虑到大租户潜在的数据扩展需求,有时也许会有相反的成本耗用结论。而多租户采用的选择,主要是成本原因,对于多数场景而言,共享度越高,软硬件资源的利用效率更好,成本也更低。但同时也要解决好租户资源共享和隔离带来的安全与性能、扩展性等问题。毕竟,也有客户无法满意于将数据与其他租户放在共享资源中。目前市面上各类数据厂商在多租户的支持上,大抵都是遵循上文所述的这几类模式,或者混合了几种策略,这部分内容将在下面介绍。JPA ProviderJSR 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 对多租户数据层进行分析。HibernateHibernate 是一个开放源代码的对象/关系映射框架和查询服务。它对 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&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. 获取当前
tenantIdpublic 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();
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = connectionProvider.getConnection();
connection.createStatement().execute("USE " + tenantIdentifier);
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 {
//getters and setters
}我们使用 ServiceSchemaBasedMain.java 来进行测试,并假设了一些数据以方便演示,如当有不同租户的管理员登录后分别进行添加客户的操作。清单 5. 测试类
ServiceSchemaBasedMainpublic class ServiceSchemaBasedMain {
public static void main(String[] args) {
Session session =
Guest guest =
List&Guest& list =
Transaction tx =
System.out.println("======== 租户 hotel_1 ========");
Login.setTenantId("hotel_1");
session = sessionFactory.openSession();
tx = session.beginTransaction();
guest = new Guest();
guest.setName("张三");
guest.setTelephone("");
guest.setAddress("上海市张扬路88号");
guest.setEmail("");
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("");
guest.setAddress("上海市南京路100号");
guest.setEmail("");
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=, address=上海科苑路399号, email=]
Guest [id=2, name=Jacky, telephone=, address=上海金科路28号, email=]
Guest [id=3, name=张三, telephone=, address=上海市张扬路88号, email=]
======== 租户 hotel_2 ========
Guest [id=1, name=Anton, telephone=, address=上海南京路8号, email=]
Guest [id=2, name=Gus, telephone=, address=北京大道3号, email=]
Guest [id=3, name=李四, telephone=, address=上海市南京路100号, email=]模式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清单 7. 对象关系映射文件
Room.hbm.xml&hibernate-mapping default-lazy="true" package="hotel.model"&
&class name="Room" table="room"&
&id name="id" column="id" type="int" unsaved-value="0"&
&generator class="native" /&
&property name="serialNumber" column="serial_number"/&
&property name="position" column="position"/&
&property name="status" column="status"/&
&many-to-one name="tenant" class="Tenant" column="tenant_id" access="field" not-null="true" lazy="false"/&
&many-to-one name="category" class="Category" column="category_id" access="field" not-null="true" lazy="false"/&
&filter name="tenantFilter" condition="tenant_id = :tenantFilterParam" /&
&filter-def name="tenantFilter"&
&filter-param name="tenantFilterParam" type="string" /&
&/filter-def&
&/hibernate-mapping&接下来我们在 HibernateUtil 类中通过 ThreadLocal 存放和获取 Hibernate Session,并将用户登录信息中的 tenantId 设置为
tenantFilterParam 的参数值。清单 8. 获取 Hibernate Session 的工具类
HibernateUtil清单 8. 获取 Hibernate Session 的工具类
HibernateUtilpublic class HibernateUtil {
public static final ThreadLocal&Session& session = new ThreadLocal&Session&();
public static Session currentSession() throws HibernateException {
Session s = session.get();
if (s == null) {
s = sessionFactory.openSession();
String tenantId = LoginContext.getTenantId();
s.enableFilter("tenantFilter").setParameter("tenantFilterParam", tenantId);
session.set(s);
}不过 Filter 只是有助于我们读取数据时显示地忽略掉 tenantId,但在进行数据插入的时候,我们还是不得不显式设置相应 tenantId
才能进行持久化。这种状况只能在 Hibernate5 版本中得到根本改变。清单 9. 运行程序 HotelServiceMain
输出清单 9. 运行程序 HotelServiceMain
输出======当前可用房间列表======
Room [ID=1, 房间编号=R1011, 位置=南楼, 床数=2, 状态=Free, 租户=新亚酒店]
Room [ID=7, 房间编号=R7011, 位置=南楼, 床数=2, 状态=Free, 租户=新亚酒店]
======预订后======
RentHistory [ID=1, 开始时间= 12:42:15.0, 结束时间= 12:42:15.0, Room ID=1, 房间编号=R1011, 房间状态=Booked, 金额=0.0元, 客人=Victor]
======结账后======
RentHistory [ID=1, 开始时间= 12:42:15.0, 结束时间= 12:42:15.0, Room ID=1, 房间编号=R1011, 房间状态=Free, 金额=300.0元, 客人=Victor]多租户下的 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 配置文件设置并启用它,以提高多租户应用的性能。EclipseLinkEclipseLink 是 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 操作上
MultitenantType value
多租户策略,SINGLE_TABLE,
TABLE_PER_TENANT, VPD.
SINGLE_TABLE 共享数据表(SINGLE_TABLE)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&属性配置租户区分列定义好后,在运行时环境需要配置具体属性值,以确定当前操作环境所属的租户。三种方式的属性配置,按优先生效顺序排序如下 EntityManager(EM) EntityManagerFactory(EMF) 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清单 14. 初始化
EntityManagerFactoryHashMap properties = new HashMap();
properties.put("tenant_id", "人力资源部");
EntityManager em = Persistence.createEntityManagerFactory("multi-tenant", properties).createEntityManager();按共享粒度可以作如下区分, EntityManagerFactory 级别用户需要通过 eclipselink.session-name 提供独立的会话名,确保每个租户占有独立的会话和缓存。清单 15. 为 EntityManagerFactory
配置会话名清单 15. 为 EntityManagerFactory
配置会话名HashMap properties = new HashMap();
properties.put("tenant_id", "开发部");
properties.put("eclipselink.session-name", "multi-tenant-dev");
EntityManager em = Persistence.createEntityManagerFactory("multi-tenant", properties).createEntityManager(); 共享的 EntityManagerFactory 级别EntityManagerFactory 的默认模式, 此级别缺省配置为独立二级缓存(L2 cache), 即每个 mutlitenant 实体缓存设置为
ISOLATED,用户也可设置 eclipselink.multitenant.tenants-share-cache 属性为真以共享,此时多租户 Entity
缓存设置为 PROTECTED。这种级别下,一个活动的 EntityManager 不能更换 tenantId。 EntityManager 级别这种级别下,共享 session,共享 L2 cache, 用户需要自己设置缓存策略,以设置哪些租户信息是不能在二级缓存共享的。清单 16.
设置缓存清单 16.
设置缓存HashMap tenantProperties = new HashMap();
tenantProperties.put("tenant_id", "人力资源部");
HashMap cacheProperties = new HashMap();
cacheProperties.put("eclipselink.cache.shared.Employee", "false");
cacheProperties.put("eclipselink.cache.size.Address", "10");
cacheProperties.put("eclipselink.cache.type.Contract", "NONE");
EntityManager em = Persistence.createEntityManagerFactory("multi-tenant", cacheProperties).createEntityManager(tenantProperties);同样,一个活动的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 名来定义表的租户区分符。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。属性配置与另外两种多租户类型一样,默认情况下,多租户共享EMF,如不想共享 EMF,可以通过配置
PersistenceUnitProperties.MULTITENANT_SHARED_EMF 以及
PersistenceUnitProperties.SESSION_NAME 实现。清单
20.// Shared EMF
EntityManager em = createEntityManager(MULTI_TENANT_PU);
em.getTransaction().begin();
em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "RLT");
// Non shared EMF
HashMap properties = new HashMap();
properties.put(PersistenceUnitProperties.MULTITENANT_SHARED_EMF, "false");
properties.put(PersistenceUnitProperties.SESSION_NAME, "non-shared-emf-for-rlt");
properties.put(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, "RLT");
EntityManager em = Persistence.createEntityManagerFactory("multi-tenant-pu", properties).createEntityManager();或在 persistence.xml 配置属性。酒店多租户应用实例(EclipseLink 共享(单)表)数据库 Schema 和测试数据与上文 Hibernate 实现相同,关于对象关系映射(OR mapping)的配置均采用 JPA 和 EclipseLink 定义的
Java Annotation 描述。关于几个基本操作 添加一个对象实例, 利用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);
} 更新一个对象实例, 利用EntityManager.merge()清单 22.
更新public &T& void update(T t){
em.merge(t);
} 查询, 利用EntityManager的NamedQuery,清单 23.
多条件多结果查询清单 23.
多条件多结果查询protected &T& List&T& queryResultList(String queryName, Class&T& clazz, String[] argNames, Object[] argValues){
TypedQuery&T& query = em.createNamedQuery(queryName, clazz);
if(argNames != null && argValues != null){
if(argNames.length != argValues.length){
for(int i = 0; i & argNames. i++){
query.setParameter(argNames[i], argValues[i]);
return query.getResultList();
}若用 JPQL 实现则示例如下:清单 24. JPQL NamedQuery
定义清单 24. JPQL NamedQuery
定义@Entity
@Table(name = "rent_history")
@Multitenant
@TenantDiscriminatorColumn(name="tenant_id", contextProperty="tenant.id")
@NamedQueries({
@NamedQuery(
name="find_renthistory_by_hotel_guest_name",
query="select h from RentHistory h, HotelGuest g where h.hotelGuestId=g.id and g.name=:hotelGuestName order by h.createTime DESC"
public class RentHistory implements Serializable {
}部分测试数据如下(MySQL):hotel_adminhotel_guestroom运行附件 MT_Test_Hotels.zip 中的测试代码(请参照 readme)来看看多租户的一些典型场景。清单 25.
运行测试代码清单 25.
运行测试代码java -classpath build/classes/;D:/workspace/eclipselink/jlib/jpa/javax.persistence_2.1.0
.v.D:/workspace/eclipselink/jlib/eclipselink.D:/workspace/mysql/mysql-connector-java-5.1.26/mysql-connector-java-5.1.26-bin.jar mtsample.hotel.test.TestHotelAdmin能得到输出片段如下:清单 26.
输出清单 26.
输出*****登录*****
用户名:letian
letian 已登录.
*****查房*****
1.获取可用房间
Room [id=3, categoryId=3, createTime= 23:13:11.0, position=南楼, serialNumber=R2011, status=Free]
Room [id=4, categoryId=4, createTime= 09:07:02.0, position=北楼, serialNumber=R2012, status=Free]
*****入住*****
请输入房间号:R2012
请输入房客编号:4
请输入入住时间(yyyy.MM.dd H:mm): 14:02
请输入退房时间(yyyy.MM.dd H:mm): 11:00
*****查房*****
1.获取可用房间
Room [id=3, categoryId=3, createTime= 13:02:49.0, position=南楼, serialNumber=R2011, status=Free]
*****退房*****
请输入房间号:R2012
请输入退房时间(yyyy.MM.dd H:mm): 5:20
1.获取所有入住历史信息
RentHistory [id=3, amount=260.0, createTime= 13:07:48.0, endTime=Sat Feb 15 05:20:00 CST 2014, hotelGuestId=4, roomId=4, startTime=Fri Feb 14 14:02:00
CST 2014, ]
RentHistory [id=1, amount=1040.0, createTime= 13:28:00.0, endTime=Fri Sep 20 11:28:00 CST 2013, hotelGuestId=3, roomId=3, startTime=Wed Sep 18 13:28:00 CST 2013, ]
*****查房*****
1.获取可用房间
Room [id=3, categoryId=3, createTime= 13:02:49.0, position=南楼, serialNumber=R2011, status=Free]
Room [id=4, categoryId=4, createTime= 13:07:48.0, position=北楼, serialNumber=R2012, status=Free]通过共享表的测试数据以及运行结果可以看到,对于多个不同的租户(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,共享数据表 多租户 Discriminator 计划在
Hibernate 5.0 支持
支持, 使用 SINGLE_TABLE MultitenantType 以及
TenantDiscriminatorColumn
下载描述名字大小代码示例272k
MSDN 的文章
系统总结了不同数据隔离模式的优缺点。
Hibernate 官网
提供了 Hibernate 最新信息及相关资源。
Hibernate 4.2 参考文档 。
Hibernate 4.2 开发指南 ,其中
描述了对不同数据隔离的支持细节。
EclipseLink 官网
提供了 EclipseLink 最新信息及相关资源。
EclipseLink 2.5 解决方案指南 。
EclipseLink 2.5 概念理解指南 。
EclipseLink 2.5 JPA 扩展参考 。
定义了 JPA 2.1 规范。 :这里有数百篇关于 Java
编程各个方面的文章。 加入 。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。
developerWorks: 登录
标有星(*)号的字段是必填字段。
保持登录。
单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件。
在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。
所有提交的信息确保安全。
选择您的昵称
当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。昵称长度在 3 至 31 个字符之间。
您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。
标有星(*)号的字段是必填字段。
(昵称长度在 3 至 31 个字符之间)
单击提交则表示您同意developerWorks 的条款和条件。 .
所有提交的信息确保安全。
文章、教程、演示,帮助您构建、部署和管理云应用。
立即加入来自 IBM 的专业 IT 社交网络。
为灾难恢复构建应用,赢取现金大奖。
static.content.url=/developerworks/js/artrating/SITE_ID=10Zone=Java technologyArticleID=957583ArticleTitle=数据层的多租户浅谈publish-date=

我要回帖

更多关于 数据库schema 的文章

 

随机推荐