多租户--EclipseLink实现

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,目前 EclipseLink2.5 完全支持 JPA2.1 。
在完整实现 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子句特性),实现多租户共享表。

Demo

我们重点介绍我们在平台中使用的一种模式:一租户一个表(也可以理解为一个租户一个Schema)的实现方法。

这种多租户类型使每个租户的数据可以占据专属它自己的一个或多个表,多租户间的这些表可以共享相同 Schema 也可使用不同的,前者使用前缀(prefix)或后缀(suffix)命名模式的表的租户区分符,后者使用租户专属的 Schema 名来定义表的租户区分符。

@Multitenant注解

与 @Entity 或 @MappedSuperclass 一起使用,表明它们在一个应用程序中被多租户共享。

@Entity
@Table(name="room")
@Multitenant
...
Public class Room {
}

Multitenant 包含两个属性:
1. boolean includeCriteria: 是否将租户限定应用到 select、update、delete 操作上 ,默认值为:true
2. MultitenantType value: 多租户策略,SINGLE_TABLE(共享表), SINGLE_TABLE
TABLE_PER_TENANT(独立Schema), VPD.

实体类:

package mtsample.hotel.model;

import java.io.Serializable;
import java.sql.Timestamp;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

import org.eclipse.persistence.annotations.Multitenant;
import org.eclipse.persistence.annotations.MultitenantType;
import org.eclipse.persistence.annotations.TenantTableDiscriminator;
import org.eclipse.persistence.annotations.TenantTableDiscriminatorType;

/** * * @ClassName: HotelGuest * @Description: 入住客户信息实体 * @author wyj * @date 2015年6月20日 下午9:20:36 * */

@Entity
@Table(name="hotel_guest")
//设置该实体为多租户共享
@Multitenant(value=MultitenantType.TABLE_PER_TENANT)
//设置多租户模式为SCHEMA
@TenantTableDiscriminator(type=TenantTableDiscriminatorType.SCHEMA, contextProperty="tenant_id")

public class HotelGuest implements Serializable {
    private static final long serialVersionUID = 1L;
      //主键及主键生成策略
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(unique=true, nullable=false)

    private int id;

    @Column(nullable=false, length=255)
    private String address;

    @Column(name="create_time", nullable=false)
    private Timestamp createTime;

    @Column(nullable=false, length=64)
    private String name;

    @Column(nullable=false, length=64)
    private String telephone;

    @Column(name="tenant_id", nullable=true, insertable=false, updatable=false, length=50)
    private String tenantId;

    public HotelGuest() {
    }

    public int getId() {
        return this.id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getAddress() {
        return this.address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public Timestamp getCreateTime() {
        return this.createTime;
    }

    public void setCreateTime(Timestamp createTime) {
        this.createTime = createTime;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getTelephone() {
        return this.telephone;
    }

    public void setTelephone(String telephone) {
        this.telephone = telephone;
    }

// public String getTenantId() {
// return this.tenantId;
// }
//
// public void setTenantId(String tenantId) {
// this.tenantId = tenantId;
// }

    @Override
    public String toString() {
        return "HotelGuest [id=" + id + ", "
                + (address != null ? "address=" + address + ", " : "")
                + (createTime != null ? "createTime=" + createTime + ", " : "")
                + (name != null ? "name=" + name + ", " : "")
                + (telephone != null ? "telephone=" + telephone + ", " : "")
                + "]";
    }

}

persistence.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="MT_HOTEL_SERVICE" transaction-type="JTA">
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        <jta-data-source>java:jboss/datasources/CloudMysqlDS</jta-data-source>
        <properties>
            <!-- 修改第一次加载时间长的问题 -->
            <property name="eclipselink.deploy-on-startup" value="true" />
            <!-- 修改为FINE后,控制台会打印出执行的sql语句,方便调试 -->
            <property name="eclipselink.logging.level" value="FINE" />          
            <property name="eclipselink.jdbc.allow-native-sql-queries" value="true" />
            <!-- 设置服务器类型 -->
            <property name="eclipselink.target-server" value="JBoss" />
            <!-- logging -->
            <property name="eclipselink.logging.level" value="SEVERE" />
            <property name="eclipselink.weaving" value="static" />
<!-- 自定义主键 UUID -->
            <property name="eclipselink.session.customizer" value="com.tgb.itoo.base.util.uuid.UUIDSequence" />
        </properties>
    </persistence-unit>
</persistence>

BaseDao类

package mtsample.hotel.dao;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;

import org.eclipse.persistence.config.EntityManagerProperties;
/** * 底层类 * @author wyj * 说明:该类封装了底层几乎所有的方法。 * 时间:2015年6月20日 */
public class BaseDao {
    //注入persistence-unit
   @PersistenceContext(unitName="MT_HOTEL_SERVICE")
    private EntityManager em ;//= null;

    //保存实体(主要测试方法
    public <T> void save(T t) {
            //动态设置该用户的标示ID。
// em.setProperty("tenant_id", "GE_LIN");
            getEntityManager().persist(t);

    }

    protected EntityManager getEntityManager() {
       //每次动态设置该用户的标示ID,根据前台传过来的数据库名称,动态的切库
         em.setProperty("tenant_id", "GE_LIN");
        return this.em;
        }
}

说明:
1. TenantTableDiscriminatorType有 3 种类型:SCHEMA(独立库)、SUFFIX(表加前缀) 和 PREFIX(后缀)。

2.默认情况下,多租户共享EMF,如不想共享 EMF,可以通过配置PersistenceUnitProperties.MULTITENANT_SHARED_EMF 以及 PersistenceUnitProperties.SESSION_NAME 实现。

// 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();

总结:

从上面的例子我们可以看到,EclipseLink完全支持JPA,可以使用注解的方式注入数据源,容器管理EntityManagerFactory,容器管理事务。同时根据不同的用户登录,在运行时动态的切换数据源,生成的jpql语句格式是:

select h from GE_LIN.t_RentHistory h, GE_LIN.t_HotelGuest g where h.hotelGuestId=g.id and g.name=:hotelGuestName order by h.createTime DESC

大家应该还记得上篇文章写道hibernate对多租户的支持时,生成的sql语句?比较不难发现,eclipseLink对多租户的支持更加细腻,可以细化到表。

以上只是对eclipseLink的简单应用,深入的学习还在继续,大家有什么新的发现可以及时交流。

你可能感兴趣的:(eclipse,云平台,多租户)