Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "EclipseLink/DesignDocs/Multi-Tenancy/TablePerTenant"

(Table per tenant)
(updated correct property "eclipselink.tenant-id")
 
(152 intermediate revisions by 3 users not shown)
Line 3: Line 3:
 
</div>
 
</div>
  
This feature will be a continuation of the multitenant feature that currently offers a SINGLE_TABLE type only. [http://wiki.eclipse.org/EclipseLink/Development/Indigo/Multi-Tenancy]
+
This feature will be a continuation of the multitenant feature that currently offers a SINGLE_TABLE or VPD type, see [http://wiki.eclipse.org/EclipseLink/Development/Indigo/Multi-Tenancy Multitenant]
 +
 
 +
Bug: [https://bugs.eclipse.org/bugs/show_bug.cgi?id=376603 376603]
  
 
== Table per tenant ==
 
== Table per tenant ==
  
The goal of this feature is to allow multiple application tenants to have a their own individual table per tenant. Table per tenant tables are allowed with shareds table within the same persistence unit. A table per tenant policy is set per individual entities.
+
The goal of this feature is to allow multiple tenants of an application to isolate their data within individual table(s). These tables can be stored within the same schema using a prefix or suffix naming pattern or can be stored in separate schemas. Table per tenant entities can be mixed with other multitenant type entities within the same persistence unit.
 +
 
 +
=== JPA Tenant Scope ===
 +
 
 +
This feature is intended to address the situation where a single application instance with a shared EntityManagerFactory for a persistence unit is responsible for handling requests from multiple tenants. In the case where separate EntityManagerFactory instances are used for each tenant (required when using extensions per tenant) then the developer can alternatively choose to specify tenant specific schema and table names within an eclipselink-orm.xml configuration file which is used based on the MetadataSource feature.
  
 
=== Requirements ===
 
=== Requirements ===
  
 
# Support configuration of table per tenant entity types using EclipseLink specific annotations and/or eclipselink-orm.xml with the XML overriding the annotation.
 
# Support configuration of table per tenant entity types using EclipseLink specific annotations and/or eclipselink-orm.xml with the XML overriding the annotation.
#* Augment database queries to limit query results to the tenant per table
+
# Ensure all READ, INSERT, UPDATE, DELETE operations populate and limit their effect to the defined table per tenant table(s)
#* Ensure all INSERT, UPDATE, DELETE operations populate and limit their effect to the defined table per tenant
+
# Tenants by default will share the same server session (tenant identifier must be updated/set per entity manager)
# Support accessing shared data at either the EntityManagerFactory or EntityManager
+
## Id generation is assumed to be unique across all table per tenants. @See Future Section.
#* When using EMF the underlying cache must be unique to the provided tenant discriminator value(s)
+
# Tenant tables can be isolated by schema or a uniqe name (with a prefix or suffix on the table names).
# Schema generation??? TBD
+
  
== Metadata Configuration ==
+
Not supported:
 +
# Schema generation will not be supported since it requires knowledge of all the tenants (schema's) and further to that, access provision must be set once the tables are created if using schema level table per tenant.
  
This document will focus only on the TABLE_PER_TENANT multi-tenant type. The type will enable individual tenant table(s) usage at the entity level using a context property that must be provided by the user.
+
== Metadata ==
  
* The TABLE_PER_TENANT states that the table(s) (Table and SecondaryTable) for the given entity are individual tenant tables based on the user.
+
This document will focus only on the TABLE_PER_TENANT multi-tenant type. The type will enable individual tenant table(s) usage at the entity level using a tenant context property that must be provided by the user on each entity manager after a transaction has started.
* A TABLE_PER_TENANT type is used in conjunction with a table per tenant property definition to identify the user.
+
  
==== Important notes ====
+
* The TABLE_PER_TENANT states that the table(s) (Table and SecondaryTable) for the given entity are individual tenant tables based on the tenant context.
+
** Relationships within that entity that use a join or collection table are also assumed to exist within the table per tenant context.
 +
* A TABLE_PER_TENANT type is used in conjunction with:
 +
** A tenant table discriminator that specifies the type of discriminator (schema or name with prefix or suffix)
 +
** A tenant id to identify the user (configured by the user per EM or at the EMF if isolating table per tenant per persistence unit)
 
* Multi-tenant metadata can only be applied at the root level of the inheritance hierarchy when using a SINGLE_TABLE or JOINED inheritance strategy. A log warning will be issued otherwise.  
 
* Multi-tenant metadata can only be applied at the root level of the inheritance hierarchy when using a SINGLE_TABLE or JOINED inheritance strategy. A log warning will be issued otherwise.  
* It is possible to specify multi-tenant metadata within a TABLE_PER_CLASS inheritance hierarchy.
+
** It is possible to specify multi-tenant metadata within a TABLE_PER_CLASS inheritance hierarchy.
  
 
==== Short example ====
 
==== Short example ====
Line 36: Line 44:
 
@Table(name=“EMP”)
 
@Table(name=“EMP”)
 
@Multitenant(TABLE_PER_TENANT)
 
@Multitenant(TABLE_PER_TENANT)
 +
@TenantTableDiscriminator(type=SCHEMA, contextProperty="eclipselink.tenant-id")
 
public class Employee {
 
public class Employee {
 
     ...
 
     ...
 
}
 
}
 +
 +
<entity class="Employee">
 +
  <multitenant type="TABLE_PER_TENANT">
 +
    <tenant-table-discriminator type="SCHEMA" context-property="eclipselink.tenant-id"/>
 +
  </multitenant>
 +
  <table name="EMP">
 +
  ...
 +
</entity>
 +
 
</source>
 
</source>
  
Line 75: Line 93:
 
|}
 
|}
  
 
+
=== Annotations ===
 
+
/////////// WORK IN PROGRESS ///////////////
+
  
 
The following new EclipseLink metadata will be added.
 
The following new EclipseLink metadata will be added.
  
=== Annotation usage ===
+
The new type will be added to the possible value list for the existing @Multitenant annotation. See [http://wiki.eclipse.org/EclipseLink/Development/Indigo/Multi-Tenancy Multinancy] for more configuration documentations of this annotation.  
 
+
The new @Multitenant annotation is specified at the entity (or mapped superclass level). To enable multi-tenancy on a given entity the @Multitenant must be specified (@see minimal configuration).
+
 
+
From the multi-tenant type, its companion annotation(s) (e.g. @TenantDiscriminatorColumn) are determined and processed accordingly.
+
  
Multitenant metadata can be applied at the mapped superclass level. When applied at this level it is applied to all sub-entities unless they specify their own multi-tenant metadata. Note: multi-tenancy and its companion annotation(s) are homogeneous and will continue to be when new types are included. Meaning the following:
+
This means we will extend the MultitenantType enum to include TABLE_PER_TENANT and add a new TenantTableDiscriminator annotation and TenantTableDiscriminatorType enum.
  
 
<source lang="java">
 
<source lang="java">
@MappedSuperclass
+
public enum MultitenantType {
@Multitenant(SINGLE_TABLE)
+
@TenantDiscriminatorColumn(name = "TENANT_ID")
+
@TenantTableDiscriminator(...)
+
public MySuperclass {
+
}
+
</source>
+
 
+
Implies that all subclasses that do not define @Multitenant will default to:
+
 
+
<source lang="java">
+
@Multitenant(SINGLE_TABLE)
+
@TenantDiscriminatorColumn(name = "TENANT_ID")
+
</source>
+
 
+
and the @TenantTableDiscriminator metadata is SILENTLY ignored and will not apply to sub-entities that define @Multitenant(TABLE_PER_TENANT).
+
 
+
Another use case, given the following:
+
 
+
<source lang="java">
+
@MappedSuperclass
+
@Multitenant(SINGLE_TABLE)
+
public MySuperclass {
+
}
+
 
+
@Entity
+
@TenantDiscriminatorColumn(name = "T_ID", contextProperty = "my.tenant.id")
+
public MyEntity {
+
}
+
</source>
+
 
+
MyEntity's @TenantDiscriminatorColumn is ignored and the defaults will apply to the @Multitenant specification from MySuperclass (where the tenant discriminator column is defaulted).
+
 
+
==== Annotation definitions ====
+
 
+
<source lang="java">
+
@Target({TYPE})
+
@Retention(RUNTIME)
+
public @interface Multitenant {
+
 
     /**
 
     /**
     * (Optional) Specify the multi-tenant strategy to use.
+
     * Specifies that table(s) the entity maps to includes rows for multiple tenants.
 +
    * The tenant discriminator column(s) are used with application context values to
 +
    * limit what a persistence context can access.
 
     */
 
     */
     MultitenantType value() default MultitenantType.SINGLE_TABLE;
+
     SINGLE_TABLE,
}
+
  
@Target({TYPE})
 
@Retention(RUNTIME)
 
public @interface TenantDiscriminatorColumn {
 
 
     /**
 
     /**
     * (Optional) The name of column to be used for the discriminator.
+
     * Specifies that the DB will handle the tenant filtering on all SELECT,
 +
    * UPDATE and DELETE queries. Using this type assumes that the platform
 +
    * used with your persistence unit does indeed support VPD.
 +
    *
 +
    * @since 2.3.1
 
     */
 
     */
     String name() default "TENANT_ID";
+
     VPD
  
     /**
+
     /**  
     * (Optional) The name of the context property to apply to the
+
     * Specifies that different tables are used for each tenant and used in
     * tenant discriminator column.
+
     * conjunction with the tenant table discriminator which describes how the
 +
    * tables are uniquely identified, that is, using a suffix/prefix or a
 +
    * separate schema.
 +
    *
 +
    * @since 2.4
 
     */
 
     */
     String contextProperty() default "eclipselink.tenant-id";
+
     TABLE_PER_TENANT
 +
}
  
 +
@Target({TYPE})
 +
@Retention(RUNTIME)
 +
public @interface TenantTableDiscriminator {
 
     /**
 
     /**
     * (Optional) The type of object/column to use as a class discriminator.
+
     * (Optional) The name of the context property to apply to as  
     * Defaults to {@link DiscriminatorType#STRING DiscriminatorType.STRING}.
+
     * tenant table discriminator. Default is "eclipselink.tenant-id"
 
     */
 
     */
     DiscriminatorType discriminatorType() default DiscriminatorType.STRING;
+
     String contextProperty() default PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT;
 
+
   
 
     /**
 
     /**
     * (Optional) The SQL fragment that is used when generating the DDL
+
     * (Optional) The type of tenant table discriminator to use with the tables
     * for the discriminator column. Defaults to the provider-generated
+
     * of the persistence unit.
     * SQL to create a column of the specified discriminator type.
+
     * Defaults to {@link TenantTableDiscriminatorType#SUFFIX TenantTableDiscriminatorType.SUFFIX}.
 
     */
 
     */
     String columnDefinition() default "";
+
     TenantTableDiscriminatorType type() default TenantTableDiscriminatorType.SUFFIX;
 +
}
  
 +
public enum TenantTableDiscriminatorType {
 
     /**
 
     /**
     * (Optional) The column length for String-based discriminator types.
+
     * Apply the tenant table discriminator as a schema to all multitenant tables.
     * Ignored for other discriminator types.
+
     * NOTE: this strategy requires appropriate database provisioning.
 
     */
 
     */
     int length() default 31;
+
     SCHEMA,
 
+
   
 
     /**
 
     /**
     * (Optional) The name of the table that contains the column.
+
     * Apply the tenant table discriminator as a suffix to all multitenant tables. This
     * If absent the column is assumed to be in the primary table.
+
     * is the default strategy.
 
     */
 
     */
     String table() default "";
+
     SUFFIX,
 
+
   
 
     /**
 
     /**
     * Specifies that the tenant discriminator column is part of the primary
+
     * Apply the tenant table discriminator as a prefix to all multitenant tables.
    * key definition of the table.
+
 
     */
 
     */
     boolean primaryKey() default false;
+
     PREFIX
}
+
 
+
@Target({TYPE})
+
@Retention(RUNTIME)
+
public @interface TenantDiscriminatorColumns {
+
  /**
+
    * (Required) One or more <code>TenantDiscriminatorColumn</code> annotations.
+
    */
+
  TenantDiscriminatorColumn[] value();
+
}
+
 
+
public enum MultitenantType {
+
    /**
+
    * Specifies that table(s) the entity maps to includes rows for multiple tenants.
+
    * The tenant discriminator column(s) are used with application context values to
+
    * limit what a persistence context can access.
+
    */
+
    SINGLE_TABLE,
+
 
+
    /**
+
    * Specifies that different tables are used for each tenant. The table scan be uniquely
+
    * identified by name, schema/tablespace.
+
    */
+
    TABLE_PER_TENANT
+
 
}
 
}
 
</source>
 
</source>
  
=== XML usage ===
+
=== XML ===
  
The multitenant metadata in XML will be available at within the entity and mapped superclass element. When support for other multi-tenant types are added, their companion metadata should be surrounded by an xml choice element.  
+
The multitenant-type XML element will be expanded to include the new type and a new tenant table discriminator complex type (with associated simple type enum equivalent) will be added. The new table tenant discriminator will be available from the existing multitenant complex type, within a choice with the existing tenant discriminator column element.
 
+
==== Eclipselink-orm.xml definition ====
+
  
 
<source lang="XML">
 
<source lang="XML">
 
  <xsd:complexType name="entity">
 
    <xsd:annotation>
 
      <xsd:documentation>
 
      ...
 
      </xsd:documentation>
 
    </xsd:annotation>
 
    <xsd:sequence>
 
      ...
 
      <xsd:element name="multitenant" type="orm:multitenant" minOccurs="0"/>
 
      ...
 
    </xsd:sequence>
 
    ...
 
  </xsd:complexType>
 
 
  <xsd:complexType name="mapped-superclass">
 
    <xsd:annotation>
 
      <xsd:documentation>
 
      ...
 
      </xsd:documentation>
 
    </xsd:annotation>
 
    <xsd:sequence>
 
      ...
 
      <xsd:element name="multitenant" type="orm:multitenant" minOccurs="0"/>
 
      ...
 
    </xsd:sequence>
 
    ...
 
  </xsd:complexType>
 
 
<!-- **************************************************** -->
 
 
<xsd:complexType name="multitenant">
 
  <xsd:annotation>
 
    <xsd:documentation>
 
      ...
 
    </xsd:documentation>
 
  </xsd:annotation>
 
  <xsd:sequence>
 
    <xsd:element name="tenant-discriminator-column" type="orm:tenant-discriminator-column" minOccurs="0" maxOccurs="unbounded"/>
 
  </xsd:sequence>
 
  <xsd:attribute name="type" type="orm:multitenant-type"/>
 
</xsd:complexType>
 
 
<!-- **************************************************** -->
 
 
<xsd:complexType name="tenant-discriminator-column">
 
  <xsd:annotation>
 
    <xsd:documentation>
 
      ...
 
    </xsd:documentation>
 
  </xsd:annotation>
 
  <xsd:attribute name="name" type="xsd:string"/>
 
  <xsd:attribute name="context-property" type="xsd:string"/>
 
  <xsd:attribute name="discriminator-type" type="orm:discriminator-type"/>
 
  <xsd:attribute name="column-definition" type="xsd:string"/>
 
  <xsd:attribute name="table" type="xsd:string"/>
 
  <xsd:attribute name="length" type="xsd:int"/>
 
  <xsd:attribute name="primary-key" type="xsd:boolean"/>
 
</xsd:complexType>
 
 
<!-- **************************************************** -->
 
 
 
<xsd:simpleType name="multitenant-type">
 
<xsd:simpleType name="multitenant-type">
  <xsd:annotation>
 
    <xsd:documentation>
 
      ...
 
    </xsd:documentation>
 
  </xsd:annotation>
 
 
   <xsd:restriction base="xsd:token">
 
   <xsd:restriction base="xsd:token">
 
     <xsd:enumeration value="SINGLE_TABLE"/>
 
     <xsd:enumeration value="SINGLE_TABLE"/>
 +
    <xsd:enumeration value="VPD"/>
 
     <xsd:enumeration value="TABLE_PER_TENANT"/>
 
     <xsd:enumeration value="TABLE_PER_TENANT"/>
 
   </xsd:restriction>
 
   </xsd:restriction>
 
</xsd:simpleType>
 
</xsd:simpleType>
  
</source>
+
<xsd:complexType name="tenant-table-discriminator">
 
+
   <xsd:attribute name="type" type="orm:tenant-table-discriminator-type"/>
=== Minimal Configuration ===
+
 
+
All parts of the multi-tenant and tenant discriminator metadata are defaulted (see annotation definition above), however to enable multi-tenancy @Multitenant or <multitenant> must be specified. Therefore the minimal configuration is:
+
 
+
<source lang="java">
+
@Entity
+
@Table(name="EMP")
+
@Multitenant
+
public Employee() {
+
  ...
+
}
+
 
+
 
+
// NOTE: The following example is NOT a minimal configuration. This would lead to no multi-tenancy as the solo @TableDiscriminator is
+
// ignored since @Multitenant was not specified.
+
 
+
@Entity
+
@Table(name="EMP")
+
@TenantDiscriminatorColumn
+
public Employee() {
+
  ...
+
}
+
</source>
+
 
+
<source lang="xml">
+
<entity class="model.Employee">
+
  <multitenant/>
+
  <table name="EMP"/>
+
  ...
+
</entity>
+
</source>
+
 
+
=== Persistence unit and entity mappings defaulting ===
+
 
+
Along with the availability from the entity and mapped superclass levels, the new metadata will available at the following levels to provide defaults. Usage at the these levels follows similar JPA metadata defaulting and overriding rules.
+
 
+
* persistence-unit-defaults
+
* entity-mappings
+
 
+
==== persistence-unit-defaults ====
+
 
+
In the eclipselink-orm.xml, it is possible to specify default tenant discriminator column metadata through the persistence unit metadata defaults. When defined at this level, it will apply to all entities of the persistence unit that have specified a multi-tenant type of SINGLE_TABLE minus those that specify their own tenant discriminator metadata.
+
 
+
Note: with no defaults, an entity not marked with multi-tenant metadata will not use any multi-tenancy strategy.
+
 
+
<source lang="xml">
+
<xsd:complexType name="persistence-unit-defaults">
+
   ...
+
    <xsd:sequence>
+
    ...
+
      <xsd:element name="tenant-discriminator-column" type="orm:tenant-discriminator-column" minOccurs="0" maxOccurs="unbounded"/>
+
    ...
+
    </xsd:sequence>
+
 
</xsd:complexType>
 
</xsd:complexType>
</source>
 
  
==== entity-mappings ====
+
<xsd:simpleType name="tenant-table-discriminator-type">
 +
  <xsd:restriction base="xsd:token">
 +
    <xsd:enumeration value="SCHEMA"/>
 +
    <xsd:enumeration value="SUFFIX"/>
 +
    <xsd:enumeration value="PREFIX"/>
 +
  </xsd:restriction>
 +
</xsd:simpleType>
  
Alternatively, users may specify tenant discriminator column metadata at the entity-mappings level as well which would override a persistence unit default and apply itself to all entities with a multitenant type of SINGLE_TABLE of the given mapping file (unless an individual entity has specified its own tenant discriminator metadata).
+
<xsd:complexType name="multitenant">
 
+
  <xsd:sequence>
<source lang="xml">
+
     <xsd:choice>
<xsd:element name="entity-mappings">
+
      <xsd:element name="tenant-discriminator-column" type="orm:tenant-discriminator-column" minOccurs="0" maxOccurs="unbounded"/>
  ...
+
      <xsd:element name="tenant-table-discriminator" type="orm:tenant-table-discriminator" minOccurs="0"/>
     <xsd:sequence>
+
     </xsd:choice>
    ...
+
  </xsd:sequence>
      <xsd:element name="tenant-discriminator-column" type="orm:tenant-discriminator-column" minOccurs="0" maxOccurs="unbounded"/>
+
  <xsd:attribute name="type" type="orm:multitenant-type"/>
    ...
+
  <xsd:attribute name="include-criteria" type="xsd:boolean"/>
     </xsd:sequence>
+
 
</xsd:complexType>
 
</xsd:complexType>
</source>
 
  
=== Mapped vs. Unmapped Tenant Discriminator ===
 
 
* When a tenant discriminator column is mapped, its associated mapping attribute should be marked as read only. If it is not, an exception will be raised. With this restriction in place, a tenant discriminator column can not be part of the entity identifier (it can only be part of the primary key specification on the database, see the annotation definition above)
 
* On persist, the value of the mapped tenant discriminator column mapping is populated from its associated context property.
 
* Both mapped and unmapped properties are used to form the additional criteria when issuing a select query.
 
* Unmapped tenant discriminator columns will require EclipseLink to populate the row with the tenant discriminator columns associated context property value. See Core section below.
 
 
=== Metadata Processing Warnings and Exceptions ===
 
 
* When multi-tenant metadata is applied to subclasses of an entity hierarchy (JOINED or SINGLE_TABLE) a log warning will be issued (and the metadata is IGNORED)
 
** NOTE: multi-tenant metadata can be provided in a TABLE_PER_CLASS inheritance hierarchy.
 
* When multiple properties map the same column.
 
* Duplicate tenant discriminator columns will log a warning message, e.g.
 
** @TenantDiscriminatorColumn(name="TENANT")
 
** @TenantDiscriminatorColumn(name="TENANT", contextProperty="eclipselink.tenant-id")
 
* A mapped tenant discriminator column who's attribute is not marked read only will throw an exception.
 
 
Defaults will always apply even when there are multiple tenant discriminators and no exception above has been raise. This allows users to map several columns for the same property. E.g. The code below would default the property name to <b>"eclipselink.tenant-id"</b> and states it should be writing the <b>TENANT</b> column for both the EMPLOYEE and SALARY table.
 
 
<source lang="java">
 
@Entity
 
@Table(name = "EMPLOYEE")
 
@SecondaryTable(name = "SALARY")
 
@MultiTenant(SINGLE_TABLE)
 
@TenantDiscriminatorColumns({
 
    @TenantDiscriminatorColumn(name = "TENANT")
 
    @TenantDiscriminatorColumn(name = "TENANT", table = "SALARY")
 
  }
 
)
 
public Employee() {
 
  ...
 
}
 
 
</source>
 
</source>
  
=== More Examples ===
+
=== Processing ===
 +
The new annotations and xml will plug into the existing metadata processing architecture.
  
==== Annotation examples ====
+
* eclipselink_orm_2_4.xsd
<source lang="java">
+
** schema updated with new xml elements
 +
*org.eclipse.persistence.internal.jpa.metadata.multitenant.TenantTableDiscriminatorMetadata
 +
** Handle the new TenantTableDiscriminator annotation and xml elements
 +
* org.eclipse.persistence.internal.jpa.metadata.xml.XMLEntityMappingsMappingProject
 +
** The MOXy mapping file modified to map the XML to the TenantTableDiscriminatorMetadata class.
 +
* org.eclipse.persistence.internal.jpa.metadata.multitenant.MultitenantMetadata
 +
** Modified to process the new TENANT_PER_TABLE multitenant type and tenant table discriminator metadata.
 +
* org.eclipse.persistence.internal.jpa.metadata.MetadataLogger
 +
** Expanded for any warning or defaulting log messaages.
  
/** Single discriminator tenant column **/
+
=== Warnings and Exceptions ===
  
@Entity
+
Relationships from a non table per tenant entity to a table per tenant entity will not be allowed. Relationships can only emanate from the table per tenant entity.
@Table(name = "CUSTOMER")
+
@Multitenant
+
@TenantDescriminatorColumn(name = "TENANT", contextProperty = "multi-tenant.id")
+
public Customer() {
+
  ...
+
}
+
  
/** Multiple tenant discriminator columns using multiple tables **/
+
NOTE: See future section. We could likely support this using the same cloning strategy we are using with table per tenant descriptors. See the core section below where we discuss the cloning of descriptors. Those entities that refer to table per tenant entities could essentially have their descriptors (and mappings) cloned and modified to refer to the table per tenant descriptor clones.
  
@Entity
+
== Properties ==
@Table(name = "EMPLOYEE")
+
@SecondaryTable(name = "RESPONSIBILITIES")
+
@Multitenant(SINGLE_TABLE)
+
@TenantDiscriminatorColumns({
+
    @TenantDiscriminatorColumn(name = "TENANT_ID", contextProperty = "employee-tenant.id", length = 20)
+
    @TenantDiscriminatorColumn(name = "TENANT_CODE", contextProperty = "employee-tenant.code", discriminatorType = STRING, table = "RESPONSIBILITIES")
+
  }
+
)
+
public Employee() {
+
  ...
+
}
+
  
/** Tenant discriminator column mapped as part of the primary key on the database **/
+
The table per tenant context property is specified by the user and we will re-use default MULTITENANT_PROPERTY_DEFAULT = "eclipselink.tenant-id".
  
@Entity
+
=== Configuration and caching ===
@Table(name = "ADDRESS")
+
@Multitenant
+
@TenantDiscriminatorColumn(name = "TENANT", contextProperty = "tenant.id", primaryKey = true)
+
public Address() {
+
  ...
+
}
+
  
/** Mapped tenant discriminator column **/
+
The caching strategy remains the same as currently implemented for the other multitenant types. By default, tenants will share the entity manager factory.
  
@Entity
+
If this is not the desired behavior, the following existing properties can set in the persistence unit definition:
@Table(name = "Player")
+
* PersistenceUnitProperties.MULTITENANT_SHARED_EMF = "eclipselink.multitenant.tenants-share-emf"
@Multitenant
+
* PersistenceUnitProperties.SESSION_NAME = "eclipselink.session-name"
@TenantDiscriminatorColumn(name = "AGE", contextProperty = "tenant.age")
+
public Player() {
+
  ...
+
  
  @Basic
+
Note: when not sharing the EMF, a unique session name must be provided and that persistence unit will be isolated to that tenant with full caching capabilities. There is currently no automatic augmentation of the session name since there is no way to be able to determine the tenant id property since the name of the property can be configured by the user (and there can be multiples etc.). A deploy time there is no way to know before processing the metadata which of course is done after the session have been created.
  @Column(name="AGE", insertable="false", updatable="false")
+
  public int age;
+
}
+
  
</source>
+
In a shared entity manager factory approach, a tenant id must be provided on each entity manager after a transaction has begun. Setting the tenant id must be the first operation on the entity manager and the tenant id must not be changed during the lifespan of that entity manager.
  
==== XML examples ====
+
By default, tenants will not share the cache, meaning table per tenant entities will have an ISOLATED setting. To allow these entities to share the cache, the following property can be set to true:
  
<source lang="xml">
+
* PersistenceUnitProperties.MULTITENANT_SHARED_CACHE = "eclipselink.multitenant.tenants-share-cache"
  
<!-- Single tenant discriminator column -->
+
When the cache is shared, table per tenant entities will have a PROTECTED setting.
  
<entity class="model.Customer">
+
=== Examples ===
  <multitenant>
+
    <tenant-discriminator-column name="TENANT context-property="multi-tenant.id""/>
+
  </multitenant>
+
  <table name="CUSTOMER"/>
+
  ...
+
</entity>
+
  
<!-- Multiple tenant discriminator columns using multiple tables -->
+
<source lang="java">
 
+
// Shared EMF
<entity class="model.Employee">
+
EntityManager em = createEntityManager(MULTI_TENANT_PU);
  <multitenant type="SINGLE_TABLE">
+
em.getTransaction().begin();
    <tenant-discriminator-column name="TENANT_ID" context-property="employee-tenant.id" length="20"/>
+
em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "gpelleti");
    <tenant-discriminator-column name="TENANT_CODE" context-property="employee-tenant.id" discriminator-type="STRING" table="RESPONSIBILITIES"/>
+
  </multitenant>
+
  <table name="EMPLOYEE"/>
+
  <secondary-table name="RESPONSIBILITIES"/>
+
  ...
+
</entity>
+
 
+
<!-- Tenant discriminator column mapped as part of the primary key on the database -->
+
 
+
<entity class="model.Address">
+
  <multitenant>
+
    <tenant-discriminator-column name="TENANT" context-property="multi-tenant.id" primary-key="true"/>
+
  </multitenant>
+
  <table name="ADDRESS"/>
+
  ...
+
</entity>
+
 
+
<!-- Mapped tenant discriminator column -->
+
 
+
<entity class="model.Player">
+
  <multi-tenant>
+
    <tenant-discriminator-column name="AGE" context-property="tenant.age"/>
+
  </multi-tenant>
+
  <table name="PLAYER"/>
+
  ...
+
  <attributes>
+
    <basic name="age" insertable="false" updatable="false">
+
      <column name="AGE"/>
+
    </basic>
+
    ...
+
  </attributes>
+
  ...
+
</entity>
+
  
 +
// Non shared EMF
 +
HashMap properties = new HashMap();
 +
properties.put(PersistenceUnitProperties.MULTITENANT_SHARED_EMF, "false");
 +
properties.put(PersistenceUnitProperties.SESSION_NAME, "non-shared-emf-for-gpelleti");
 +
properties.put(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, "gpelleti");
 +
...   
 +
EntityManager em = Persistence.createEntityManagerFactory("multi-tenant-pu", properties).createEntityManager();
 
</source>
 
</source>
  
== Property configuration and caching scope ==
+
In the persistence unit definition (non shared-emf)
 
+
At runtime the properties can be specified via a persistence unit definition or passed to a create entity manager factory call.
+
 
+
The order of precendence for tenant discriminator column properties is as follows:
+
* EntityManager
+
* EntityManagerFactory
+
* Application context (when in a Java EE container)
+
  
 
<source lang="xml">
 
<source lang="xml">
<persistence-unit name="multi-tenant">
+
<persistence-unit name="multi-tenant-pu">
 
   ...
 
   ...
 
   <properties>
 
   <properties>
     <property name="tenant.id" value="707"/>
+
     <property name="eclipselink.multitenant.tenants-share-emf" value="false"/>
 +
    <property name="eclipselink.session-name" value="non-shared-emf-for-gpelleti"/>
 +
    <property name="eclipselink.tenant-id" value="gpelleti"/>
 
     ...
 
     ...
 
   </properties>
 
   </properties>
 
</persistence-unit>
 
</persistence-unit>
</source>
 
 
Or alternatively (and most likely preferred) in code as follows:
 
 
<source lang="java">
 
HashMap properties = new HashMap();
 
properties.put("tenant.id", "707");
 
...   
 
EntityManager em = Persistence.createEntityManagerFactory("multi-tenant", properties).createEntityManager();
 
</source>
 
 
==== Entity Manager Factory ====
 
 
At this level, users will be required to provide a unique session name through the "eclipselink.session-name" property to ensure a unique server session (and cache) is provided for each tenant. This allows for user defined properties (without any prefixing). In further iterations we will look to augment the session name automatically for the user based on their tenant property values (or something thereof).
 
 
<source lang="java">
 
HashMap properties = new HashMap();
 
properties.put("tenant.id", "707");
 
properties.put("eclipselink.session-name", "multi-tenant-707");
 
...   
 
EntityManager em = Persistence.createEntityManagerFactory("multi-tenant", properties).createEntityManager();
 
</source>
 
 
===== Shared Entity Manager Factory =====
 
 
A shared entity manager factory will be configured by default. When using a shared entity manager factory, by default the cache (L2) is not shared, meaning multitenant entities will have an ISOLATED cache setting. If users want to share the cache, setting the eclipselink.multitenant.tenants-share-cache to true will allow this and multitenant entities will have a PROTECTED cache setting.
 
 
To not share the entity manager factory users can set the eclipselink.multitenant.tenants-share-emf property to false.
 
 
Swapping tenant ID during an active EntityManager is not allowed.
 
 
==== Entity Manager ====
 
 
At this level, users will be required to specify the caching strategies as the same server session can be employed for each tenant. Users may decide to us an isolation level here etc to ensure no 'shared' tenant information exists in the L2 cache. These settings are set when creating the entity manager factory.
 
 
Swapping tenant id during a live EntityManager is not allowed.
 
 
<source lang="java">
 
HashMap tenantProperties = new HashMap();
 
properties.put("tenant.id", "707");
 
 
HashMap cacheProperties = new HashMap();
 
properties.put("eclipselink.cache.shared.Employee", "false");
 
properties.put("eclipselink.cache.size.Address", "10");
 
properties.put("eclipselink.cache.type.Contract", "NONE");
 
...   
 
EntityManager em = Persistence.createEntityManagerFactory("multi-tenant", cacheProperties).createEntityManager(tenantProperties);
 
...
 
 
</source>
 
</source>
  
 
== Core ==
 
== Core ==
  
The tenant dsicriminator column(s) will be initialized during the pre-initialization of each descriptor of the persistence unit.
+
=== Implementation ===
  
Those columns will then be applied in two places.
+
A table per tenant policy will be created which will implement the existing MultitenantPolicy. This policy will be responsible for the initialization of table per tenant descriptors and the table name translations.
 +
* org.eclipse.persistence.descriptors.TablePerMultitenantPolicy
  
# We will leverage the current additional join expression from the DescriptorQueryManager to filter tenants. This is similar to the [[EclipseLink/Development/AdditionalCriteria | Additional Criteria]] feature. During postInitialization of the descriptor query manager after we have appended the additional criteria (if there is some), we will append the tenant discriminator column(s) to the additional join expression.
+
The meat of a schema table per tenant strategy will be achieved through database level settings and access provisions. The table per tenant property will correlate directly with EclipseLink's descriptor table qualifier (which is added to all tables relating to that descriptor, table and secondary table).  
## The tenant discriminator column value(s) will be added as arguments when issuing queries.
+
# For inserts, we will append the tenant discriminator column(s) and value(s) when building the row representation of an object. This is done in the following methods from ObjectBuilder (Note: this is similar to the handling of the discriminator column within an inheritance hierarchy)
+
## buildRow
+
## buildRowForShallowInsert
+
## buildRowForUpdate
+
## buildRowWithChangeSet
+
## buildTemplateInsertRow
+
#* NOTE: When the tenant discriminator column is mapped, it need not be added to the row. Only its value should be populated if it has not already been done.
+
# For reading, to enable the additional criteria (tenant discriminator columns) we will need to modify the ReadObjectQuery and ReadAllQuery slightly in the prepareForExecution method.
+
## There we will clone the translation row (if need be) and append the tenant discriminator columns to it before execution.
+
The tenant discriminator column(s) are assumed to exist on the primary table. If using secondary tables the tenant discriminator column metadata must specify the table if it is not on the primary.
+
  
Tenant discriminator column(s) are not expected for the following tables (which refer back to their related entity through a primary key association):
+
The policy will also be responsible for applying this table qualifier setting to all mappings of the descriptor that use a relation table (Many to many, one to one etc.). When those mappings are created, there is no notion of a table qualifier/schema at that point. Since the table per tenant entity is the owner of the relationship it is only fair to assume that its relation table must also have the same qualifier. It is entirely possible though (with unique id's across all tenants for table per tenant entities) to not have this restriction/assumption in place however. See future section.
  
# @CollectionTable
+
Under the same assumption, element collection tables should also have the table per tenant identifier as well, but again not necessary when unique id's are employed across all tenants.
# @JoinTable
+
# JOINED inheritance hierarchy tables  
+
# SINGLE_TABLE inheritance hierarchy
+
  
NOTE: Id generation is shared across persistence units (see future section below).
+
In the case of a prefix or suffix solution, instead of setting a table qualifier as described above, all table names from the cloned descriptor and its mappings will need to be updated to the new table name employing the table per tenant property. This name translation will be done by the policy.
  
==== Core/Runtime Exceptions ====
+
ClassDescriptor:
* An exception will be thrown when a named tenant property can not be found.
+
* Will need its clone method expanded to include any missing parts not already included in that method.
  
== Properties ==
+
Project:
 +
* When using a table per tenant entities within a shared EMF, the project will be cloned per client session. This allows us to clone the jpql parse cache and allow its usage for that session. Otherwise, we would need to disable it. The cloned jpql cache will be created with the same max size as the parents.
  
The public default context property (eclipselink.tenant-id) definition will be available from:
+
ServerSession:
 +
* Will contain the metadata processor produced class descriptor.
 +
* These descriptors are available from the existing descriptor maps and list and will also be paired out to their own list (for ease of manipulation)
 +
* Unless the table per tenant property is set at the EMF level, table per tenant descriptors will never be initialized. Instead the table per tenant descriptors will cloned for each client session, updated with the table per tenant context property and those cloned descriptor are then initialized and used for the lifespan of that client session.
 +
* When the table per tenant is provided at the EMF level, the server session descriptors are fully initialized and never cloned per client session. Normal initializing operations will occur in this case. Setting a table per tenant at the EM level here is not allowed and an exception will be thrown.
  
* org.eclipse.persistence.config.EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT
+
=== Exceptions ===
* org.eclipse.persistence.config.PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT
+
  
=== Java example ===
+
An exception will be raised,
<source lang="java">
+
* when a table per tenant is not set (database exception in tis case, i.e. invalid table name etc.)
EntityManager em = createEntityManager(MULTI_TENANT_PU);
+
em.setProperty("tenant.id", "707");
+
em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "707");
+
</source>
+
  
 
== Querying ==
 
== Querying ==
  
The tenant discriminator column and value will be supported through the following entity manager operations:
+
Full querying should be available to the user through the following entity manager operations:
  
 
* persist
 
* persist
Line 630: Line 323:
 
* delete all
 
* delete all
  
NOTE: EclipseLink will not modify, therefore, support multi-tenancy through named native queries. When using these types of queries within a multi-tenant environment, the user will need to be aware and handle any multi-tenancy issues themselves directly in their native query. To all intent and purpose, named native queries should be avoided in a multi-tenant environment.
+
NOTE: EclipseLink will not modify, therefore, support multi-tenancy through named native queries. When using these types of queries within a multi-tenant environment, the user will need to be aware and handle any multi-tenancy issues (schemas and table names) themselves directly in their native query. For all intents and purposes, named native queries should be avoided in a multi-tenant environment.
  
 
== DDL generation ==
 
== DDL generation ==
  
DDL generation will need to support the generation of tenant discriminator columns (for all necessary tables). The DDL generation of columns is based off the descriptor's columns. During pre-initialization we therefore need to ensure that our tenant discriminator columns are built and added to this list (if they are NOT mapped columns). This should be done after the descriptor table initialization (including inheritance hierarchies) has been preformed. Mapped tenant discriminator columns are added automatically and we should avoid adding them more than once.
+
DDL generation will not be supported. Since we a table for all tenants plus the access provisions, there is no way for use to know this information before hand.
 
+
<pre>
+
if (hasTenantDiscriminatorFields()) {
+
    for (DatabaseField discriminatorField : tenantDiscriminatorFields.keySet()) {
+
        getFields().add(buildField(discriminatorField));
+
    }
+
}
+
</pre>
+
 
+
=== DefaultTableGenerator ===
+
 
+
Only a small change will be required to the DDL generator to ensure we capture a primary key setting for a tenant discriminator column. The following line will be added to the initTableSchema(ClassDescriptor) method:
+
 
+
<source lang="java">
+
isPKField = isPKField || dbField.isPrimaryKey();
+
</source>
+
  
 
== Open/Future items ==
 
== Open/Future items ==
  
# [https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458 Bug 355458]: Provide admin user access data from multiple tenants
+
* A flag or setting to indicate per mapping if its associated table is isolated per tenant or not. This opens up caching issues though, in that, we could never use the level 2 cache and would always have to force the table per tenant entities to an isolated session if the ids are not unique across tenants?? Some unknowns here.
# Tenant column when part of the entity identifier
+
* Allow for relationships from non table per tenant entities to table per tenant entities. Would need to do more descriptor cloning per entity manager (client session) but this is possible.
## Incorporate sequence generators
+
* Add tenant table discriminator to entity mappings and persistence unit defaults of eclipselink_orm_2_4.xsd
# Augment the session name (or something of the sort) for the user removing the dependency of providing a unique session name to achieve cache isolation.
+
* Specifying a catalog of schema in the table discriminator

Latest revision as of 22:44, 9 June 2012

This feature will be a continuation of the multitenant feature that currently offers a SINGLE_TABLE or VPD type, see Multitenant

Bug: 376603

Table per tenant

The goal of this feature is to allow multiple tenants of an application to isolate their data within individual table(s). These tables can be stored within the same schema using a prefix or suffix naming pattern or can be stored in separate schemas. Table per tenant entities can be mixed with other multitenant type entities within the same persistence unit.

JPA Tenant Scope

This feature is intended to address the situation where a single application instance with a shared EntityManagerFactory for a persistence unit is responsible for handling requests from multiple tenants. In the case where separate EntityManagerFactory instances are used for each tenant (required when using extensions per tenant) then the developer can alternatively choose to specify tenant specific schema and table names within an eclipselink-orm.xml configuration file which is used based on the MetadataSource feature.

Requirements

  1. Support configuration of table per tenant entity types using EclipseLink specific annotations and/or eclipselink-orm.xml with the XML overriding the annotation.
  2. Ensure all READ, INSERT, UPDATE, DELETE operations populate and limit their effect to the defined table per tenant table(s)
  3. Tenants by default will share the same server session (tenant identifier must be updated/set per entity manager)
    1. Id generation is assumed to be unique across all table per tenants. @See Future Section.
  4. Tenant tables can be isolated by schema or a uniqe name (with a prefix or suffix on the table names).

Not supported:

  1. Schema generation will not be supported since it requires knowledge of all the tenants (schema's) and further to that, access provision must be set once the tables are created if using schema level table per tenant.

Metadata

This document will focus only on the TABLE_PER_TENANT multi-tenant type. The type will enable individual tenant table(s) usage at the entity level using a tenant context property that must be provided by the user on each entity manager after a transaction has started.

  • The TABLE_PER_TENANT states that the table(s) (Table and SecondaryTable) for the given entity are individual tenant tables based on the tenant context.
    • Relationships within that entity that use a join or collection table are also assumed to exist within the table per tenant context.
  • A TABLE_PER_TENANT type is used in conjunction with:
    • A tenant table discriminator that specifies the type of discriminator (schema or name with prefix or suffix)
    • A tenant id to identify the user (configured by the user per EM or at the EMF if isolating table per tenant per persistence unit)
  • Multi-tenant metadata can only be applied at the root level of the inheritance hierarchy when using a SINGLE_TABLE or JOINED inheritance strategy. A log warning will be issued otherwise.
    • It is possible to specify multi-tenant metadata within a TABLE_PER_CLASS inheritance hierarchy.

Short example

@Entity
@Table(name=“EMP”)
@Multitenant(TABLE_PER_TENANT)
@TenantTableDiscriminator(type=SCHEMA, contextProperty="eclipselink.tenant-id")
public class Employee {
    ...
}
 
<entity class="Employee">
  <multitenant type="TABLE_PER_TENANT">
    <tenant-table-discriminator type="SCHEMA" context-property="eclipselink.tenant-id"/>
  </multitenant>
  <table name="EMP">
  ...
</entity>

tenant1.EMP

EMP_ID VERSION F_NAME L_NAME GENDER
1 1 John Doe M

tenant2.EMP

EMP_ID VERSION F_NAME L_NAME GENDER
2 3 Jane Doe F

Annotations

The following new EclipseLink metadata will be added.

The new type will be added to the possible value list for the existing @Multitenant annotation. See Multinancy for more configuration documentations of this annotation.

This means we will extend the MultitenantType enum to include TABLE_PER_TENANT and add a new TenantTableDiscriminator annotation and TenantTableDiscriminatorType enum.

public enum MultitenantType {
    /**
     * Specifies that table(s) the entity maps to includes rows for multiple tenants. 
     * The tenant discriminator column(s) are used with application context values to
     * limit what a persistence context can access.
     */
    SINGLE_TABLE, 
 
    /**
     * Specifies that the DB will handle the tenant filtering on all SELECT,
     * UPDATE and DELETE queries. Using this type assumes that the platform
     * used with your persistence unit does indeed support VPD.
     * 
     * @since 2.3.1
     */
    VPD
 
    /** 
     * Specifies that different tables are used for each tenant and used in 
     * conjunction with the tenant table discriminator which describes how the 
     * tables are uniquely identified, that is, using a suffix/prefix or a 
     * separate schema.
     * 
     * @since 2.4
     */
    TABLE_PER_TENANT
}
 
@Target({TYPE}) 
@Retention(RUNTIME)
public @interface TenantTableDiscriminator {
    /**
     * (Optional) The name of the context property to apply to as 
     * tenant table discriminator. Default is "eclipselink.tenant-id"
     */
    String contextProperty() default PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT;
 
    /**
     * (Optional) The type of tenant table discriminator to use with the tables
     * of the persistence unit.
     * Defaults to {@link TenantTableDiscriminatorType#SUFFIX TenantTableDiscriminatorType.SUFFIX}.
     */
    TenantTableDiscriminatorType type() default TenantTableDiscriminatorType.SUFFIX;
}
 
public enum TenantTableDiscriminatorType {
    /**
     * Apply the tenant table discriminator as a schema to all multitenant tables.
     * NOTE: this strategy requires appropriate database provisioning.
     */
    SCHEMA, 
 
    /**
     * Apply the tenant table discriminator as a suffix to all multitenant tables. This
     * is the default strategy.
     */
    SUFFIX, 
 
    /**
     * Apply the tenant table discriminator as a prefix to all multitenant tables.
     */
    PREFIX
}

XML

The multitenant-type XML element will be expanded to include the new type and a new tenant table discriminator complex type (with associated simple type enum equivalent) will be added. The new table tenant discriminator will be available from the existing multitenant complex type, within a choice with the existing tenant discriminator column element.

<xsd:simpleType name="multitenant-type">
  <xsd:restriction base="xsd:token">
    <xsd:enumeration value="SINGLE_TABLE"/>
    <xsd:enumeration value="VPD"/>
    <xsd:enumeration value="TABLE_PER_TENANT"/>
  </xsd:restriction>
</xsd:simpleType>
 
<xsd:complexType name="tenant-table-discriminator">
  <xsd:attribute name="type" type="orm:tenant-table-discriminator-type"/>
</xsd:complexType>
 
<xsd:simpleType name="tenant-table-discriminator-type">
  <xsd:restriction base="xsd:token">
    <xsd:enumeration value="SCHEMA"/>
    <xsd:enumeration value="SUFFIX"/>
    <xsd:enumeration value="PREFIX"/>
  </xsd:restriction>
</xsd:simpleType>
 
<xsd:complexType name="multitenant">
  <xsd:sequence>
    <xsd:choice>
      <xsd:element name="tenant-discriminator-column" type="orm:tenant-discriminator-column" minOccurs="0" maxOccurs="unbounded"/>
      <xsd:element name="tenant-table-discriminator" type="orm:tenant-table-discriminator" minOccurs="0"/>
    </xsd:choice>
  </xsd:sequence>
  <xsd:attribute name="type" type="orm:multitenant-type"/>
  <xsd:attribute name="include-criteria" type="xsd:boolean"/>
</xsd:complexType>

Processing

The new annotations and xml will plug into the existing metadata processing architecture.

  • eclipselink_orm_2_4.xsd
    • schema updated with new xml elements
  • org.eclipse.persistence.internal.jpa.metadata.multitenant.TenantTableDiscriminatorMetadata
    • Handle the new TenantTableDiscriminator annotation and xml elements
  • org.eclipse.persistence.internal.jpa.metadata.xml.XMLEntityMappingsMappingProject
    • The MOXy mapping file modified to map the XML to the TenantTableDiscriminatorMetadata class.
  • org.eclipse.persistence.internal.jpa.metadata.multitenant.MultitenantMetadata
    • Modified to process the new TENANT_PER_TABLE multitenant type and tenant table discriminator metadata.
  • org.eclipse.persistence.internal.jpa.metadata.MetadataLogger
    • Expanded for any warning or defaulting log messaages.

Warnings and Exceptions

Relationships from a non table per tenant entity to a table per tenant entity will not be allowed. Relationships can only emanate from the table per tenant entity.

NOTE: See future section. We could likely support this using the same cloning strategy we are using with table per tenant descriptors. See the core section below where we discuss the cloning of descriptors. Those entities that refer to table per tenant entities could essentially have their descriptors (and mappings) cloned and modified to refer to the table per tenant descriptor clones.

Properties

The table per tenant context property is specified by the user and we will re-use default MULTITENANT_PROPERTY_DEFAULT = "eclipselink.tenant-id".

Configuration and caching

The caching strategy remains the same as currently implemented for the other multitenant types. By default, tenants will share the entity manager factory.

If this is not the desired behavior, the following existing properties can set in the persistence unit definition:

  • PersistenceUnitProperties.MULTITENANT_SHARED_EMF = "eclipselink.multitenant.tenants-share-emf"
  • PersistenceUnitProperties.SESSION_NAME = "eclipselink.session-name"

Note: when not sharing the EMF, a unique session name must be provided and that persistence unit will be isolated to that tenant with full caching capabilities. There is currently no automatic augmentation of the session name since there is no way to be able to determine the tenant id property since the name of the property can be configured by the user (and there can be multiples etc.). A deploy time there is no way to know before processing the metadata which of course is done after the session have been created.

In a shared entity manager factory approach, a tenant id must be provided on each entity manager after a transaction has begun. Setting the tenant id must be the first operation on the entity manager and the tenant id must not be changed during the lifespan of that entity manager.

By default, tenants will not share the cache, meaning table per tenant entities will have an ISOLATED setting. To allow these entities to share the cache, the following property can be set to true:

  • PersistenceUnitProperties.MULTITENANT_SHARED_CACHE = "eclipselink.multitenant.tenants-share-cache"

When the cache is shared, table per tenant entities will have a PROTECTED setting.

Examples

// Shared EMF
EntityManager em = createEntityManager(MULTI_TENANT_PU);
em.getTransaction().begin();
em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "gpelleti");
 
// Non shared EMF
HashMap properties = new HashMap();
properties.put(PersistenceUnitProperties.MULTITENANT_SHARED_EMF, "false");
properties.put(PersistenceUnitProperties.SESSION_NAME, "non-shared-emf-for-gpelleti");
properties.put(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, "gpelleti");
...     
EntityManager em = Persistence.createEntityManagerFactory("multi-tenant-pu", properties).createEntityManager();

In the persistence unit definition (non shared-emf)

<persistence-unit name="multi-tenant-pu">
  ...
  <properties>
    <property name="eclipselink.multitenant.tenants-share-emf" value="false"/>
    <property name="eclipselink.session-name" value="non-shared-emf-for-gpelleti"/>
    <property name="eclipselink.tenant-id" value="gpelleti"/>
    ...
  </properties>
</persistence-unit>

Core

Implementation

A table per tenant policy will be created which will implement the existing MultitenantPolicy. This policy will be responsible for the initialization of table per tenant descriptors and the table name translations.

  • org.eclipse.persistence.descriptors.TablePerMultitenantPolicy

The meat of a schema table per tenant strategy will be achieved through database level settings and access provisions. The table per tenant property will correlate directly with EclipseLink's descriptor table qualifier (which is added to all tables relating to that descriptor, table and secondary table).

The policy will also be responsible for applying this table qualifier setting to all mappings of the descriptor that use a relation table (Many to many, one to one etc.). When those mappings are created, there is no notion of a table qualifier/schema at that point. Since the table per tenant entity is the owner of the relationship it is only fair to assume that its relation table must also have the same qualifier. It is entirely possible though (with unique id's across all tenants for table per tenant entities) to not have this restriction/assumption in place however. See future section.

Under the same assumption, element collection tables should also have the table per tenant identifier as well, but again not necessary when unique id's are employed across all tenants.

In the case of a prefix or suffix solution, instead of setting a table qualifier as described above, all table names from the cloned descriptor and its mappings will need to be updated to the new table name employing the table per tenant property. This name translation will be done by the policy.

ClassDescriptor:

  • Will need its clone method expanded to include any missing parts not already included in that method.

Project:

  • When using a table per tenant entities within a shared EMF, the project will be cloned per client session. This allows us to clone the jpql parse cache and allow its usage for that session. Otherwise, we would need to disable it. The cloned jpql cache will be created with the same max size as the parents.

ServerSession:

  • Will contain the metadata processor produced class descriptor.
  • These descriptors are available from the existing descriptor maps and list and will also be paired out to their own list (for ease of manipulation)
  • Unless the table per tenant property is set at the EMF level, table per tenant descriptors will never be initialized. Instead the table per tenant descriptors will cloned for each client session, updated with the table per tenant context property and those cloned descriptor are then initialized and used for the lifespan of that client session.
  • When the table per tenant is provided at the EMF level, the server session descriptors are fully initialized and never cloned per client session. Normal initializing operations will occur in this case. Setting a table per tenant at the EM level here is not allowed and an exception will be thrown.

Exceptions

An exception will be raised,

  • when a table per tenant is not set (database exception in tis case, i.e. invalid table name etc.)

Querying

Full querying should be available to the user through the following entity manager operations:

  • persist
  • find
  • refresh

And the following queries:

  • named queries
  • update all
  • delete all

NOTE: EclipseLink will not modify, therefore, support multi-tenancy through named native queries. When using these types of queries within a multi-tenant environment, the user will need to be aware and handle any multi-tenancy issues (schemas and table names) themselves directly in their native query. For all intents and purposes, named native queries should be avoided in a multi-tenant environment.

DDL generation

DDL generation will not be supported. Since we a table for all tenants plus the access provisions, there is no way for use to know this information before hand.

Open/Future items

  • A flag or setting to indicate per mapping if its associated table is isolated per tenant or not. This opens up caching issues though, in that, we could never use the level 2 cache and would always have to force the table per tenant entities to an isolated session if the ids are not unique across tenants?? Some unknowns here.
  • Allow for relationships from non table per tenant entities to table per tenant entities. Would need to do more descriptor cloning per entity manager (client session) but this is possible.
  • Add tenant table discriminator to entity mappings and persistence unit defaults of eclipselink_orm_2_4.xsd
  • Specifying a catalog of schema in the table discriminator

Back to the top