上一篇博客中我们介绍了多租户的数据隔离,文中具体的介绍了hibernate和eclipselink对于多租户的实现情况,博客的最后,我也对hibernate实现多租户的细节上做了解释,这次,我想带大家一起来使用eclipselink构建企业级的多租户项目。
eclipselink的三种实现
由于eclipselink完整实现了jpa规范,我们就可以使用ejb构建一个企业级的多租户项目,首先eclipselink支持3中多租户的模式:
- 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子句特性),实现多租户共享表。
Table-Per-Tenant Multi-tenancy实现
- 我想将的是第二种方式,因为它在性能、分离度以及灵活性上都是比较合适的。
- 首先是persistence.xml的内容,经过上一篇博客的介绍,我们应该已经配置好eclipselink的基本使用环境。
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名字--> <persistence-unit name="MT_HOTEL_SERVICE" transaction-type="JTA"> <!-- 使用eclipselink的持久化实现--> <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> <!-- 使用jboss中配置的数据源--> <jta-data-source>java:jboss/datasources/JcMysqlDS</jta-data-source> <!-- 由于配置了实体自动扫描此处不用列出系统中的试题--> <properties> <!-- 修改第一次加载时间长的问题 --> <property name="eclipselink.deploy-on-startup" value="True" /> <!-- 运行执行本地sql查询 --> <property name="eclipselink.jdbc.allow-native-sql-queries" value="true" /> <!-- 设置服务器类型 --> <property name="eclipselink.target-server" value="JBoss" /> <!-- logging 级别配置--> <property name="eclipselink.logging.level" value="FINE" /> <!-- 配置静态织入--> <property name="eclipselink.weaving" value="static" /> <!-- 设置自定义主键生成策略--> <property name="eclipselink.session.customizer" value="com.tgb.itoo.base.util.uuid.UUIDSequence" /> </properties> </persistence-unit></persistence>
- 配置中每一行的配置都有做出解释,这里就不再多说了。
BaseEntity配置
- 对于实体的配置:首先是使用的实体继承,BaseEntity的代码如下:
package com.tgb.itoo.base.entity;import java.util.Date;import javax.persistence.Column;import javax.persistence.EntityListeners;import javax.persistence.GeneratedValue;import javax.persistence.Id;import javax.persistence.MappedSuperclass;import javax.persistence.Temporal;import javax.persistence.TemporalType;import javax.persistence.Transient;@MappedSuperclass@EntityListeners(value = { EntityListener.class })public abstract class BaseEntity implements IdEntity { private static final long serialVersionUID = 1L; public BaseEntity() { super(); } @Id @GeneratedValue(generator = "system-uuid")//自定义uuid生成策略 @Column(name = "id") protected String id; public String getId() { return id; } public void setId(String id) { this.id = id; } /** * 数据库名称 */ @Transient //不进行持久化 private String dataBaseName; public String getDataBaseName() { return dataBaseName; } public void setDataBaseName(String dataBaseName) { this.dataBaseName = dataBaseName; } /** * 备注 */ @Column(name = "comment", length = 255) private String comment; /** * 操作人 */ @Column(name = "operator", length = 20) private String operator; ……}
- BaseEntity中定义了实体的公共属性,其中重点关注两个属性值
- id:id使用的是自定义的uuid生成策略,这个在下一篇博客中介绍。
- dataBasename:从名字上来说很明了就是数据库名,但是这个属性不是用来持久化的,所以使用@Transient注解,这个属性主要是为了传递参数的,会在实现多租户的时候用到,因为对于任何实体的每一次操作都应该确定是那个承租者的操作,就要使数据的CRUD都要对应自己的数据库。
具体某个实体的设计
package com.tgb.itoo.authority.entity;import javax.persistence.Column;import javax.persistence.Entity;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;import com.tgb.itoo.base.entity.TimeBaseEntity;/** * 角色实体 * @author hanyi * */@Entity@Table(name="ta_role")@Multitenant(value=MultitenantType.TABLE_PER_TENANT)@TenantTableDiscriminator(type=TenantTableDiscriminatorType.SCHEMA, contextProperty="tenant_id")public class Role extends TimeBaseEntity { private static final long serialVersionUID = 598970641717338526L; /** * 角色名称 */ @Column(name = "roleName", length = 32) private String roleName; /** * 角色描述 */ @Column(name = "roleDescribe", length = 100) private String roleDescribe; ……}
- 应该注意实体中对于多租户的注解,其中设置了MultitenantType.TABLE_PER_TENANT也就是使用依靠表的租户区分,TenantTableDiscriminatorType有三种类型,这里使用SCHEMA,也就是每个租户使用独立的SCHEMA,定义了contextProperty=”tenant_id”,这也就设置了entitymanager可以根据tenant_id属性区别不同的租户。
entitymanager的注入
//实体管理器,管理实体持久化操作,通过容器注入 @PersistenceContext(unitName = "MT_HOTEL_SERVICE") protected EntityManager em;
设置多租户属性
/** * 根据数据库名字,得到当前操作对应的实体管理器的名字 * @return 实体管理器 */protected EntityManager getEntityManager() { //dataBaseName的值,给实体管理器赋值,此处的tenant_id是在实体注解时规定,只要统一可以是任何值。 if (dataBaseName!=null && !dataBaseName.equals("")) { em.setProperty("tenant_id", dataBaseName); } //如果dataBaseName的值为空,那么就选择一个默认的库,这里选择的是根库(base) else { em.setProperty("tenant_id", "base); } return this.em;}
具体Eao的方法
/** * @author hanyi * @MethodName : save * @Description : 无返回值的泛型保存方法 * @param t:要保存的实体 */ public void save(Object entity) { //从实体类型中拿到当前操作的数据库名字 getDataBaseName(entity); //取得实体管理器,进行持久化操作 getEntityManager().persist(entity); }
- 这里忽略了从getDataBaseName(entity)方法的代码,其主要作用是通过反射得到前台设置的databasename的值,之后再getEntityManager的时候使用。
完成配置
到此eclipselink+ejb+jpa实现企业级的多租户应用的数据层隔离也就解决了。
版权声明:本文为博主原创文章,未经博主允许不得转载。
- 1楼u010028869昨天 22:20
- 期待ing