多租戶--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的簡單應用,深入的學習還在繼續,大家有什麼新的發現可以及時交流。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章