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/UserGuide/sandbox/gelernter/Multi-Tenant Shared Schema"

m
m
Line 1: Line 1:
from http://wiki.eclipse.org/EclipseLink/Development/Indigo/Multi-Tenancy
+
= Single-Table Multi-Tenancy =
  
<div style="float:right;width:300px">
+
The <tt>SINGLE_TABLE</tt> multi-tenant type specifies that any table to which an entity or mapped
__TOC__
+
</div>
+
  
Enhancement request: [https://bugs.eclipse.org/bugs/show_bug.cgi?id=337323 bug 337323]
+
superclass maps can include rows for multiple tenants. Access to tenant-specific rows is restricted to
  
== Multi-Tenancy ==
+
the tenant.
  
The goal of this feature is to allow multiple application tenants to share the same schema using tenant discriminator column(s). It is for shared 'striped' database data and this feature will target only the SINGLE_TABLE solution outlined in the new annotation and xml metadata to follow. The TABLE_PER_TENANT strategy will not be addressed in this feature doc.
+
Tenant-specific rows are associated with the tenant by using tenant discriminator columns. The  
  
=== Requirements ===
+
''discriminator columns'' are used with application context values to limit what a persistence context
  
# Support configuration of shared multi-tenant entity types using EclipseLink specific annotations and/or eclispelink-orm.xml with the XML overriding the annotation.
+
can access.  
#* Augment database queries to limit query results to the tenant discriminator value(s) provided as property values
+
#* Ensure all INSERT, UPDATE, DELETE operations populate and limit their effect to the defined tenant discriminator column(s)
+
# Support accessing shared data at either the EntityManagerFactory or EntityManager
+
#* When using EMF the underlying cache must be unique to the provided tenant discriminator value(s)
+
# Support the tenant discriminator column(s) being:
+
#* un-mapped
+
#* mapped 
+
# Support schema generation including the specified tenant discriminator column(s).
+
  
== Metadata Configuration ==
+
The results of queries on the mapped tables are limited to the tenant discriminator value(s) provided as
  
This document will focus only on the SINGLE_TABLE multi-tenant type. The type will enable shared tenant table(s) usage at the entity level using one or more columns associated with persistence unit or context property values that must be provided by the user.
+
property values. This applies to all insert, update, and delete operations on the table. When multi-tenant metadata is applied at the mapped superclass level, it is applied to all subentities
  
=== Highlights ===
+
unless they specify their own multi-tenant metadata. ''''REVIEWERS: I didn’t include any of the info re: '''
  
* The SINGLE_TABLE states that the table(s) (Table and SecondaryTable) for the given entity is shared ("striped") amongst tenants.
+
homogeneity from the design doc, because I took it to be implementation details that will be pertinent
* A SINGLE_TABLE type is used in conjunction with tenant discriminator column(s).
+
* The tenant discriminator column(s) is completely application definable.
+
* The user can pick any property or column name they wish or let Eclipselink use defaults.
+
* There is no limit on the number of tenant discriminator column(s) an application can configure.
+
  
==== Important notes ====
+
when TABLE_PER_TENANT is available. Therefore, I left it out. Please advise if I should add any back
  
* A TABLE_PER_TENANT multi-tenant specification will cause an exception to be thrown (un-supported). 
+
in.'
* 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 ====
+
'''Note:''' In the context of single-table multi-tenancy, “single-table” means multiple tenants can share
  
<source lang="java">
+
a single table, and each tenant’s data is distinguished from other tenants’ data via the discriminator
@Entity
+
@Table(name=“EMP”)
+
@Multitenant(SINGLE_TABLE)
+
@TenantDiscriminatorColumn(name = “TENANT_ID”, contextProperty = “tenant-id”)
+
public class Employee {
+
    ...
+
}
+
</source>
+
  
{|{{BMTableStyle}}
+
column(s). This is opposed to a different kind of multi-tenancy where each tenant’s data is stored in
|-{{BMTHStyle}}
+
 
! EMP_ID
+
separate tables, and the tenant’s data is distinguished from other tenants’ data by identifying the
! VERSION
+
 
! F_NAME
+
table. It is possible to use multiple tables with single-table multi-tenancy; but in that case, an
! L_NAME
+
 
! GENDER
+
entity’s persisted data is stored in multiple, and multiple tenants can share all the tables.
! TENANT_ID
+
 
|-
+
===== @Multitenant Attributes =====
| 1
+
 
| 1
+
{|
| John
+
|-
| Doe
+
! Attribute
| M
+
! Description
| 1
+
! Default
|-
+
! Required?
| 2
+
|-
| 3
+
| <tt>MultitenantType</tt>
| Jane
+
| Specifies the multi-tenant strategy to use (<tt>SINGLE_TABLE</tt>).
| Doe
+
| &nbsp;
| F
+
| No
| 2
+
 
|}
 
|}
  
The following new EclipseLink metadata will be added.
+
===== @TenantDiscriminatorColumn Attributes =====
  
=== Annotation usage ===
+
{|
 +
|-
 +
! Attribute
 +
! Description
 +
! Default
 +
! Required?
 +
|-
 +
| <tt>columnDefinition</tt>
 +
| The SQL fragment that is used when generating the DDL for the discriminator column.
 +
| The provider-generated SQL to create a column of the specified discriminator type.
 +
| No
 +
|-
 +
| <tt>contextProperty</tt>
 +
| The name of the context property to apply to the tenant discriminator column.
 +
| <tt>eclipselink.tenant-id</tt>
 +
| No
 +
|-
 +
| <tt>discriminatorType</tt>
 +
| The type of object/column to use as a class discriminator.
 +
| <tt>javax.persistence.DiscriminatorType.STRING</tt>
 +
| No
 +
|-
 +
| <tt>length</tt>
 +
| The column length for String-based discriminator types.
 +
| The column length for String-based discriminator types. Ignored for other discriminator types.
 +
| No
 +
|-
 +
| <tt>name</tt>
 +
| The name of column to be used for the tenant discriminator.
 +
| <tt>TENANT_ID</tt>
 +
| No
 +
|-
 +
| <tt>primaryKey</tt>
 +
| Specifies that the tenant discriminator column is part of the primary key of the tables.
 +
| Specifies that the tenant discriminator column is part of the primary key of the tables.
 +
| Yes
 +
|-
 +
| <tt>table</tt>
 +
| The name of the table that contains the column.
 +
| The name of the table that contains the column. If absent the column is assumed to be in the primary
 +
table. This attribute must be specified if the column is on a secondary table.
  
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).
+
| No
 +
|}
  
From the multi-tenant type, its companion annotation(s) (e.g. @TenantDiscriminatorColumn) are determined and processed accordingly.
+
&nbsp;
  
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:
+
== Configuring Single-Table Multi-Tenancy  ==
  
<source lang="java">
+
To configure single-table multi-tenancy, you must specify both of the following:
@MappedSuperclass
+
@Multitenant(SINGLE_TABLE)
+
@TenantDiscriminatorColumn(name = "TENANT_ID")
+
@TenantTableDiscriminator(...)
+
public MySuperclass {
+
}
+
</source>
+
  
Implies that all subclasses that do not define @Multitenant will default to:
+
#Annotate the entity or mapped superclass to use single-table multi-tenancy, using the
 +
 
 +
<tt>@Multitenant</tt> annotation,
 +
<pre>@Multitenant</pre>
 +
annotation, for example:
  
 
<source lang="java">
 
<source lang="java">
 +
@Entity
 +
@Table(name=“EMP”)
 
@Multitenant(SINGLE_TABLE)
 
@Multitenant(SINGLE_TABLE)
@TenantDiscriminatorColumn(name = "TENANT_ID")
+
</source>  
</source>
+
  
and the @TenantTableDiscriminator metadata is SILENTLY ignored and will not apply to sub-entities that define @Multitenant(TABLE_PER_TENANT).
+
SINGLE_TABLE states that the table or tables (Table and SecondaryTable) associated with the given entity
  
Another use case, given the following:
+
can be shared among tenants. 2.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Specify the column or columns to be used as the discriminator column, using the  
  
<source lang="java">
+
@TenantDiscriminatorColumn annotation, for example: @Entity @Table(name=“EMP”) @Multitenant(SINGLE_TABLE) @TenantDiscriminatorColumn(name = “TENANT_ID”) &nbsp; You can specify multiple discriminator columns by using the @TableDiscriminatorColumns annotation, for
@MappedSuperclass
+
@Multitenant(SINGLE_TABLE)
+
public MySuperclass {
+
}
+
  
@Entity
+
example: &nbsp; @Entity @Table(name = "EMPLOYEE") @Multitenant(SINGLE_TABLE) @TenantDiscriminatorColumns({  
@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).
+
    @TenantDiscriminatorColumn(name = "TENANT_ID")
 +
          @TenantDiscriminatorColumn(name = "TENANT_CODE")})
  
==== Annotation definitions ====
+
&nbsp;
  
<source lang="java">
+
== Using Discriminator Columns  ==
@Target({TYPE})
+
@Retention(RUNTIME)
+
public @interface Multitenant {
+
    /**
+
    * (Optional) Specify the multi-tenant strategy to use.
+
    */
+
    MultitenantType value() default MultitenantType.SINGLE_TABLE;
+
}
+
  
@Target({TYPE})
+
The following characteristics apply to discriminator columns: &nbsp;  
@Retention(RUNTIME)
+
public @interface TenantDiscriminatorColumn {
+
    /**
+
    * (Optional) The name of column to be used for the discriminator.
+
    */
+
    String name() default "TENANT_ID";
+
  
    /**
+
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Tenant discriminator columns are completely application definable. REVIEWERS: I’m not sure what
    * (Optional) The name of the context property to apply to the
+
    * tenant discriminator column.
+
    */
+
    String contextProperty() default "eclipselink.tenant-id";
+
  
    /**
+
this sentence is after. Can I rewrite as “Discriminator columns must be defined by the application.”?
    * (Optional) The type of object/column to use as a class discriminator.
+
    * Defaults to {@link DiscriminatorType#STRING DiscriminatorType.STRING}.
+
    */
+
    DiscriminatorType discriminatorType() default DiscriminatorType.STRING;
+
  
    /**
+
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; There is no limit on how many tenant discriminator columns an application can define.  
    * (Optional) The SQL fragment that is used when generating the DDL
+
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Any name can be used for a discriminator column.  
    * for the discriminator column. Defaults to the provider-generated
+
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Tenant discriminator column(s) must always be used with @Multitenant(SINGLE_TABLE). You cannot
    * SQL to create a column of the specified discriminator type.
+
    */
+
    String columnDefinition() default "";
+
  
    /**
+
specify the tenant discriminator column(s) only.  
    * (Optional) The column length for String-based discriminator types.
+
    * Ignored for other discriminator types.
+
    */
+
    int length() default 31;
+
  
    /**
+
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Generated schemas can include specified tenant discriminator columns.  
    * (Optional) The name of the table that contains the column.
+
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Tenant discriminator columns can be mapped or unmapped:
    * If absent the column is assumed to be in the primary table.
+
    */
+
    String table() default "";
+
  
    /**
+
o&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; When a tenant discriminator column is mapped, its associated mapping attribute must be marked as
    * Specifies that the tenant discriminator column is part of the primary
+
    * key definition of the table.
+
    */
+
    boolean primaryKey() default false;
+
}
+
  
@Target({TYPE})
+
read only. With this restriction in place, a tenant discriminator column cannot be part of the entity
@Retention(RUNTIME)
+
public @interface TenantDiscriminatorColumns {
+
  /**
+
    * (Required) One or more <code>TenantDiscriminatorColumn</code> annotations.
+
    */
+
  TenantDiscriminatorColumn[] value();
+
}
+
  
public enum MultitenantType {
+
identifier; it can only be part of the primary key specification on the database. o&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; On persist, the value of a mapped tenant discriminator column mapping is populated from its
    /**
+
    * 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,
+
  
    /**
+
associated context property. o&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Both mapped and unmapped properties are used to form the additional criteria when issuing a
    * Specifies that different tables are used for each tenant. The table scan be uniquely
+
    * identified by name, schema/tablespace.
+
    */
+
    TABLE_PER_TENANT
+
}
+
</source>
+
  
=== XML usage ===
+
SELECT query. o&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; When a tenant discriminator column is not mapped, the row is populated with the tenant
  
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.
+
discriminator column’s associated context property value. REVIEWERS: I have a question about this item from the design doc: “Unmapped tenant discriminator columns will require EclipseLink to populate the row with the tenant  
  
==== Eclipselink-orm.xml definition ====
+
discriminator column’s associated context property value. See Core section below.” I am confused by this. There is also the item: “On persist, the value of a mapped tenant discriminator column mapping is populated from its associated
  
<source lang="XML">
+
context property. “ It sounds like these two might be saying the same thing for mapped and unmapped. What am I missing?
  
  <xsd:complexType name="entity">
+
== Using Single-Table Multi-Tenancy in an Inheritence Hierarchy  ==
    <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">
+
Inheritance strategies are configured by specifying the inheritance type (see @inheritence in
    <xsd:annotation>
+
      <xsd:documentation>
+
      ...
+
      </xsd:documentation>
+
    </xsd:annotation>
+
    <xsd:sequence>
+
      ...
+
      <xsd:element name="multitenant" type="orm:multitenant" minOccurs="0"/>
+
      ...
+
    </xsd:sequence>
+
    ...
+
  </xsd:complexType>
+
  
<!-- **************************************************** -->
+
javax.persistence). Single-table multi-tenancy can be used in an inheritance hierarchy, as follows:
  
<xsd:complexType name="multitenant">
+
*Multi-tenant metadata can only be applied at the root level of the inheritance hierarchy when using a
  <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>
+
  
<!-- **************************************************** -->
+
SINGLE_TABLE or JOINED inheritance strategy.
  
<xsd:complexType name="tenant-discriminator-column">
+
*It is possible to specify multi-tenant metadata within a TABLE_PER_CLASS inheritance hierarchy.
  <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>
+
  
<!-- **************************************************** -->
+
REVIEWERS: How? Is it that the metadata can be applied somewhere down the hierarchy?
  
<xsd:simpleType name="multitenant-type">
+
== Annotation Examples  ==
  <xsd:annotation>
+
    <xsd:documentation>
+
      ...
+
    </xsd:documentation>
+
  </xsd:annotation>
+
  <xsd:restriction base="xsd:token">
+
    <xsd:enumeration value="SINGLE_TABLE"/>
+
    <xsd:enumeration value="TABLE_PER_TENANT"/>
+
  </xsd:restriction>
+
</xsd:simpleType>
+
  
</source>
+
The following example defines a single discriminator tenant column: &nbsp; @Entity @Table(name = "CUSTOMER") @Multitenant @TenantDiscriminatorColumn(name = "TENANT",
  
=== Minimal Configuration ===
+
    contextProperty = "multi-tenant.id")
  
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:
+
public Customer() {
  
<source lang="java">
 
@Entity
 
@Table(name="EMP")
 
@Multitenant
 
public Employee() {
 
 
   ...
 
   ...
}
 
  
 +
} &nbsp; The following example defines multiple tenant discriminator columns using multiple tables: &nbsp; @Entity @Table(name = "EMPLOYEE") @SecondaryTable(name = "RESPONSIBILITIES") @Multitenant(SINGLE_TABLE) @TenantDiscriminatorColumns({
  
// NOTE: The following example is NOT a minimal configuration. This would lead to no multi-tenancy as the solo @TableDiscriminator is
+
    @TenantDiscriminatorColumn(name = "TENANT_ID",
// ignored since @Multitenant was not specified.
+
        contextProperty = "employee-tenant.id",
 +
        length = 20)
 +
    @TenantDiscriminatorColumn(name = "TENANT_CODE",
 +
        contextProperty = "employee-tenant.code",
 +
        discriminatorType = STRING,
 +
        table = "RESPONSIBILITIES")
 +
  }
  
@Entity
+
) public Employee() {  
@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 ===
+
} &nbsp; The following example defines a tenant discriminator column mapped as part of the primary key on the
  
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.
+
database: &nbsp; @Entity @Table(name = "ADDRESS") @Multitenant @TenantDiscriminatorColumn(name = "TENANT", contextProperty = "tenant.id",
  
* persistence-unit-defaults
+
    primaryKey = true)
* entity-mappings
+
  
==== persistence-unit-defaults ====
+
public Address() {
  
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.
+
} &nbsp; The following example defines a mapped tenant discriminator column: &nbsp; @Entity @Table(name = "Player") @Multitenant @TenantDiscriminatorColumn(name = "AGE",  
 +
 
 +
  contextProperty = "tenant.age")
 +
 
 +
public Player() {
  
<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>
 
</source>
 
  
==== entity-mappings ====
+
@Basic @Column(name="AGE", insertable="false", updatable="false") public int age; }
  
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).
+
== Specifying Metadata Using XML  ==
  
<source lang="xml">
+
You can also use XML to configure single-table multi-tenancy, as an alternative to or in addition to
<xsd:element name="entity-mappings">
+
  ...
+
    <xsd:sequence>
+
    ...
+
      <xsd:element name="tenant-discriminator-column" type="orm:tenant-discriminator-column" minOccurs="0" maxOccurs="unbounded"/>
+
    ...
+
    </xsd:sequence>
+
</xsd:complexType>
+
</source>
+
  
=== Mapped vs. Unmapped Tenant Discriminator ===
+
using annotations. Use the following elements in the eclipselink-orm.xml file: &lt;multitenant&gt; &lt;multitenant-type&gt; &lt;tenant-discriminator-column&gt; &nbsp; For the EclipseLink schemas, see http://wiki.eclipse.org/EclipseLink/XSDs. REVIEWERS: The design doc makes it look like you don’t have to specify &lt;multitenant-type&gt; when using XML.  
  
* 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)
+
Is that correct? XML Examples The following example defines a single tenant discriminator column: &lt;entity class="model.Customer"&gt;
* 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 ===
+
  &lt;multitenant&gt;
 +
    &lt;tenant-discriminator-column
 +
      name="TENANT”
  
* 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)
+
context-property="multi-tenant.id"/&gt;
** 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.
+
  &lt;/multitenant&gt;
 
+
  &lt;table name="CUSTOMER"/&gt;
<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>
 
  
=== More Examples ===
+
&lt;/entity&gt; &nbsp; &nbsp; The following example defines multiple tenant discriminator columns using multiple tables: &nbsp; &lt;entity class="model.Employee"&gt;
  
==== Annotation examples ====
+
  &lt;multitenant type="SINGLE_TABLE"&gt;
<source lang="java">
+
    &lt;tenant-discriminator-column name="TENANT_ID" context-property="employee-tenant.id" length="20"/&gt;
 +
    &lt;tenant-discriminator-column name="TENANT_CODE" context-property="employee-tenant.id"  
  
/** Single discriminator tenant column **/
+
discriminator-type="STRING" table="RESPONSIBILITIES"/&gt;
  
@Entity
+
  &lt;/multitenant&gt;
@Table(name = "CUSTOMER")
+
  &lt;table name="EMPLOYEE"/&gt;
@Multitenant
+
  &lt;secondary-table name="RESPONSIBILITIES"/&gt;
@TenantDescriminatorColumn(name = "TENANT", contextProperty = "multi-tenant.id")
+
public Customer() {
+
 
   ...
 
   ...
}
 
  
/** Multiple tenant discriminator columns using multiple tables **/
+
&lt;/entity&gt; &nbsp; The following example defines a tenant discriminator column mapped as part of the primary key on the
  
@Entity
+
database: &nbsp; &lt;entity class="model.Address"&gt;
@Table(name = "EMPLOYEE")
+
 
@SecondaryTable(name = "RESPONSIBILITIES")
+
  &lt;multitenant&gt;
@Multitenant(SINGLE_TABLE)
+
     &lt;tenant-discriminator-column name="TENANT" context-property="multi-tenant.id" primary-key="true"/&gt;
@TenantDiscriminatorColumns({
+
  &lt;/multitenant&gt;
     @TenantDiscriminatorColumn(name = "TENANT_ID", contextProperty = "employee-tenant.id", length = 20)
+
  &lt;table name="ADDRESS"/&gt;
    @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 **/
+
&lt;/entity&gt; &nbsp; The following example defines a mapped tenant discriminator column: &nbsp; &lt;entity class="model.Player"&gt;
  
@Entity
+
  &lt;multi-tenant&gt;
@Table(name = "ADDRESS")
+
    &lt;tenant-discriminator-column name="AGE" context-property="tenant.age"/&gt;
@Multitenant
+
  &lt;/multi-tenant&gt;
@TenantDiscriminatorColumn(name = "TENANT", contextProperty = "tenant.id", primaryKey = true)
+
  &lt;table name="PLAYER"/&gt;
public Address() {
+
  ...
 +
  &lt;attributes&gt;
 +
    &lt;basic name="age" insertable="false" updatable="false"&gt;
 +
      &lt;column name="AGE"/&gt;
 +
    &lt;/basic&gt;
 +
    ...
 +
  &lt;/attributes&gt;
 
   ...
 
   ...
}
 
  
/** Mapped tenant discriminator column **/
+
&lt;/entity&gt; &nbsp;
  
@Entity
+
== Property Configuration and Caching Scope  ==
@Table(name = "Player")
+
@Multitenant
+
@TenantDiscriminatorColumn(name = "AGE", contextProperty = "tenant.age")
+
public Player() {
+
  ...
+
  
  @Basic
+
At runtime, context properties can be specified in a persistence unit definition or passed to a
  @Column(name="AGE", insertable="false", updatable="false")
+
  public int age;
+
}
+
  
</source>
+
CreateEntityManagerFactory() call, as shown in the following examples.
  
==== XML examples ====
+
The order of precedence for tenant discriminator column properties is as follows, listed from highest
  
<source lang="xml">
+
priority to lowest: &lt;-- REVIEWERS: is that right? Highest to lowest?
  
<!-- Single tenant discriminator column -->
+
#EntityManager
 +
#EntityManagerFactory
 +
#Application context (when in a Java EE container)
 +
 
 +
The following example shows a property set in a persistence unit definition in the persistence.xml file: &nbsp; &lt;persistence-unit name="multi-tenant"&gt;
  
<entity class="model.Customer">
 
  <multitenant>
 
    <tenant-discriminator-column name="TENANT context-property="multi-tenant.id""/>
 
  </multitenant>
 
  <table name="CUSTOMER"/>
 
 
   ...
 
   ...
</entity>
+
  &lt;properties&gt;
 +
    &lt;property name="tenant.id" value="707"/&gt;
 +
    ...
 +
  &lt;/properties&gt;
  
<!-- Multiple tenant discriminator columns using multiple tables -->
+
&lt;/persistence-unit&gt; &nbsp; The following example shows the property set in code: &nbsp; HashMap properties = new HashMap(); properties.put("tenant.id", "707"); ... EntityManager em = Persistence.createEntityManagerFactory("multi-tenant",
  
<entity class="model.Employee">
+
properties).createEntityManager();
  <multitenant type="SINGLE_TABLE">
+
    <tenant-discriminator-column name="TENANT_ID" context-property="employee-tenant.id" length="20"/>
+
    <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 Manager Factory  ===
  
<entity class="model.Address">
+
At the level of the entity manager factory, you must provide a unique session name through the
  <multitenant>
+
    <tenant-discriminator-column name="TENANT" context-property="multi-tenant.id" primary-key="true"/>
+
  </multitenant>
+
  <table name="ADDRESS"/>
+
  ...
+
</entity>
+
  
<!-- Mapped tenant discriminator column -->
+
eclipselink.session-name property, to ensure a unique server session (and cache) is provided for each
  
<entity class="model.Player">
+
tenant. This allows for user-defined properties (without any prefixing). For example: &nbsp; HashMap properties = new HashMap(); properties.put("tenant.id", "707"); properties.put("eclipselink.session-name", "multi-tenant-707"); ... EntityManager em = Persistence.createEntityManagerFactory("multi-tenant",
  <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>
+
  
</source>
+
properties).createEntityManager(); &nbsp;
  
== Property configuration and caching scope ==
+
=== Shared Entity Manager Factory  ===
  
At runtime the properties can be specified via a persistence unit definition or passed to a create entity manager factory call.
+
When using a shared entity manager factory, you must set following property to indicate the factory will
  
The order of precendence for tenant discriminator column properties is as follows:
+
be shared: eclipselink.multitenant.tenants-share-cache
* EntityManager
+
* EntityManagerFactory
+
* Application context (when in a Java EE container)
+
  
<source lang="xml">
+
When this property is set, all multitenant entities will have a PROTECTED cache setting. Entity Manager
<persistence-unit name="multi-tenant">
+
  ...
+
  <properties>
+
    <property name="tenant.id" value="707"/>
+
    ...
+
  </properties>
+
</persistence-unit>
+
</source>
+
  
Or alternatively (and most likely preferred) in code as follows:
+
At the level of the entity manager, you must specify caching strategies, because the same server session
  
<source lang="java">
+
can be used for each tenant. For example, you can use an isolation level to ensure no shared tenant  
HashMap properties = new HashMap();
+
properties.put("tenant.id", "707");
+
...   
+
EntityManager em = Persistence.createEntityManagerFactory("multi-tenant", properties).createEntityManager();
+
</source>
+
  
==== Entity Manager Factory ====
+
information exists in the L2 cache. These settings are set when creating the entity manager factory. For
  
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).
+
example: HashMap tenantProperties = new HashMap(); properties.put("tenant.id", "707"); &nbsp; 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",
  
<source lang="java">
+
cacheProperties).createEntityManager(tenantProperties); ... Swapping tenant ID during a live EntityManager is not allowed.  
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 =====
+
== Defining Persistence Unit and Entity Mappings Defaults  ==
  
When using a shared entity manager factory, no L2 cache "striping' will be performed. The following property must be set to indicate the EMF will be shared:
+
You can define single-table multi-tenancy for specific entities and mapped superclasses, as described
  
<source lang="XML">
+
above. In addition, you can define the metadata at higher levels in the eclipselink-orm.xml file, to
eclipselink.multitenant.tenants-share-cache
+
</source>
+
  
When this property is set, all multitenant entities will have a PROTECTED cache setting.
+
provide defaults. Standard JPA metadata defaulting and overriding rules apply. The elements used for
  
==== Entity Manager ====
+
specifying these defaults are:
  
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.
+
*&lt;persistence-unit-defaults&gt;
 +
*&lt;entity-mappings&gt;
  
Swapping tenant id during a live EntityManager is not allowed.
+
=== Persistence Unit Defaults  ===
  
<source lang="java">
+
You can specify default tenant discriminator column metadata using the &lt;persistence-unit-defaults&gt;  
HashMap tenantProperties = new HashMap();
+
properties.put("tenant.id", "707");
+
  
HashMap cacheProperties = new HashMap();
+
element and its &lt;tenant-discriminator-column&gt; subelement. When defined at this level, it applies to all
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>
+
  
== Core ==
+
entities of the persistence unit that specify the SINGLE_TABLE multi-tenant type, excluding those that
  
The tenant dsicriminator column(s) will be initialized during the pre-initialization of each descriptor of the persistence unit.
+
specify their own tenant discriminator metadata. For example: REVIWERS: I came up with this example myself. Is it correct? If not, please advise.  
  
Those columns will then be applied in two places.
+
&lt;persistence-unit-metadata&gt;
  
# 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.
+
  &lt;persistence-unit-defaults&gt;
## 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)
+
    &lt;tenant-discriminator-column  
## buildRow
+
      name="TENANT”
## 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):
+
context-property="multi-tenant.id"/&gt;
  
# @CollectionTable
+
    &lt;/multitenant&gt;
# @JoinTable
+
  &lt;/persistence-unit-defaults&gt;
# JOINED inheritance hierarchy tables
+
# SINGLE_TABLE inheritance hierarchy
+
  
NOTE: Id generation is shared across persistence units (see future section below).
+
&lt;/persistence-unit-metadata&gt; &nbsp; Note: With no defaults, an entity not marked with multi-tenant metadata will not use any multi-tenancy
  
==== Core/Runtime Exceptions ====
+
strategy.  
* An exception will be thrown when a named tenant property can not be found.
+
  
== Properties ==
+
=== Entity Mappings Defaults ===
  
The public default context property (eclipselink.tenant-id) definition will be available from:
+
You can also specify tenant discriminator column metadata using &lt;entity-mappings&gt; element. This overrides
  
* org.eclipse.persistence.config.EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT
+
persistence unit defaults (described above) and applies to all entities of the given mapping file that
* org.eclipse.persistence.config.PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT
+
  
=== Java example ===
+
specify the SINGLE_TABLE multi-tenant type, excluding those entities that specify their own tenant
<source lang="java">
+
EntityManager em = createEntityManager(MULTI_TENANT_PU);
+
em.setProperty("tenant.id", "707");
+
em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "707");
+
</source>
+
  
== Querying ==
+
discriminator metadata. For example, REVIWERS: I came up with this example myself. Is it correct? If not, please advise.
  
The tenant discriminator column and value will be supported through the following entity manager operations:
+
&lt;entity-mappings &gt;
  
* persist
+
  ...
* find
+
    &lt;tenant-discriminator-column
* refresh
+
      name="TENANT”
  
And the following queries:
+
context-property="multi-tenant.id"/&gt; &lt;entity-mappings &gt;
* 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 themselves directly in their native query. To all intent and purpose, named native queries should be avoided in a multi-tenant environment.
+
=== Example ===
  
== DDL generation ==
+
Defaults always apply even when there are multiple tenant discriminators. This allows you to map several
  
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.
+
columns for the same property. For example, the code below defaults the property name to  
  
<pre>
+
"eclipselink.tenant-id" and states it should be writing the TENANT column for both the EMPLOYEE and
if (hasTenantDiscriminatorFields()) {
+
    for (DatabaseField discriminatorField : tenantDiscriminatorFields.keySet()) {
+
        getFields().add(buildField(discriminatorField));
+
    }
+
}
+
</pre>
+
  
=== DefaultTableGenerator ===
+
SALARY table. @Entity @Table(name = "EMPLOYEE") @SecondaryTable(name = "SALARY") @MultiTenant(SINGLE_TABLE) @TenantDiscriminatorColumns({
  
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:
+
    @TenantDiscriminatorColumn(name = "TENANT")
 +
    @TenantDiscriminatorColumn(name = "TENANT", table = "SALARY")
 +
  }
  
<source lang="java">
+
) public Employee() {
isPKField = isPKField || dbField.isPrimaryKey();
+
 
</source>
+
  ...
 +
 
 +
} &nbsp;
 +
 
 +
=== Public Default Context Property  ===
 +
 
 +
The public default context property (eclipselink.tenant-id) definition is available from:
 +
 
 +
*org.eclipse.persistence.config.EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT
 +
*org.eclipse.persistence.config.PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT
 +
 
 +
For example:
 +
 
 +
EntityManager em = createEntityManager(MULTI_TENANT_PU); em.setProperty("tenant.id", "707"); em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "707");  
 +
 
 +
Support for Tenant Discriminator Columns through Entity Manager Operations and Querying The tenant discriminator column and value are supported through the following entity manager operations:
 +
 
 +
*persist()
 +
*find()
 +
*refresh()
 +
 
 +
They are also supported through the following queries:
 +
 
 +
*Named queries
 +
*Update all
 +
*Delete all
 +
 
 +
REVIEWERS: Do you think any examples would be appropriate here? If so, please provide. Note: EclipseLink does not support multi-tenancy through named native queries. If you want to use named
  
== Open/Future items ==
+
native queries in a multi-tenant environment, you must handle any multi-tenancy issues directly in the
  
# Provide admin user access data from multiple tenants
+
query. In general, it is best to avoid named native queries in a multi-tenant environment. &nbsp;
# Tenant column when part of the entity identifier
+
## Incorporate sequence generators
+
# 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.
+

Revision as of 13:10, 16 June 2011

Single-Table Multi-Tenancy

The SINGLE_TABLE multi-tenant type specifies that any table to which an entity or mapped

superclass maps can include rows for multiple tenants. Access to tenant-specific rows is restricted to

the tenant.

Tenant-specific rows are associated with the tenant by using tenant discriminator columns. The

discriminator columns are used with application context values to limit what a persistence context

can access.

The results of queries on the mapped tables are limited to the tenant discriminator value(s) provided as

property values. This applies to all insert, update, and delete operations on the table. When multi-tenant metadata is applied at the mapped superclass level, it is applied to all subentities

unless they specify their own multi-tenant metadata. 'REVIEWERS: I didn’t include any of the info re:

homogeneity from the design doc, because I took it to be implementation details that will be pertinent

when TABLE_PER_TENANT is available. Therefore, I left it out. Please advise if I should add any back

in.'

Note: In the context of single-table multi-tenancy, “single-table” means multiple tenants can share

a single table, and each tenant’s data is distinguished from other tenants’ data via the discriminator

column(s). This is opposed to a different kind of multi-tenancy where each tenant’s data is stored in

separate tables, and the tenant’s data is distinguished from other tenants’ data by identifying the

table. It is possible to use multiple tables with single-table multi-tenancy; but in that case, an

entity’s persisted data is stored in multiple, and multiple tenants can share all the tables.

@Multitenant Attributes
Attribute Description Default Required?
MultitenantType Specifies the multi-tenant strategy to use (SINGLE_TABLE).   No
@TenantDiscriminatorColumn Attributes
Attribute Description Default Required?
columnDefinition The SQL fragment that is used when generating the DDL for the discriminator column. The provider-generated SQL to create a column of the specified discriminator type. No
contextProperty The name of the context property to apply to the tenant discriminator column. eclipselink.tenant-id No
discriminatorType The type of object/column to use as a class discriminator. javax.persistence.DiscriminatorType.STRING No
length The column length for String-based discriminator types. The column length for String-based discriminator types. Ignored for other discriminator types. No
name The name of column to be used for the tenant discriminator. TENANT_ID No
primaryKey Specifies that the tenant discriminator column is part of the primary key of the tables. Specifies that the tenant discriminator column is part of the primary key of the tables. Yes
table The name of the table that contains the column. The name of the table that contains the column. If absent the column is assumed to be in the primary

table. This attribute must be specified if the column is on a secondary table.

No

 

Configuring Single-Table Multi-Tenancy

To configure single-table multi-tenancy, you must specify both of the following:

  1. Annotate the entity or mapped superclass to use single-table multi-tenancy, using the

@Multitenant annotation,

@Multitenant

annotation, for example:

@Entity
@Table(name=“EMP”)
@Multitenant(SINGLE_TABLE)

SINGLE_TABLE states that the table or tables (Table and SecondaryTable) associated with the given entity

can be shared among tenants. 2.      Specify the column or columns to be used as the discriminator column, using the

@TenantDiscriminatorColumn annotation, for example: @Entity @Table(name=“EMP”) @Multitenant(SINGLE_TABLE) @TenantDiscriminatorColumn(name = “TENANT_ID”)   You can specify multiple discriminator columns by using the @TableDiscriminatorColumns annotation, for

example:   @Entity @Table(name = "EMPLOYEE") @Multitenant(SINGLE_TABLE) @TenantDiscriminatorColumns({

   @TenantDiscriminatorColumn(name = "TENANT_ID")
         @TenantDiscriminatorColumn(name = "TENANT_CODE")})

 

Using Discriminator Columns

The following characteristics apply to discriminator columns:  

  •         Tenant discriminator columns are completely application definable. REVIEWERS: I’m not sure what

this sentence is after. Can I rewrite as “Discriminator columns must be defined by the application.”?

  •         There is no limit on how many tenant discriminator columns an application can define.
  •         Any name can be used for a discriminator column.
  •         Tenant discriminator column(s) must always be used with @Multitenant(SINGLE_TABLE). You cannot

specify the tenant discriminator column(s) only.

  •         Generated schemas can include specified tenant discriminator columns.
  •         Tenant discriminator columns can be mapped or unmapped:

o       When a tenant discriminator column is mapped, its associated mapping attribute must be marked as

read only. With this restriction in place, a tenant discriminator column cannot be part of the entity

identifier; it can only be part of the primary key specification on the database. o       On persist, the value of a mapped tenant discriminator column mapping is populated from its

associated context property. o       Both mapped and unmapped properties are used to form the additional criteria when issuing a

SELECT query. o       When a tenant discriminator column is not mapped, the row is populated with the tenant

discriminator column’s associated context property value. REVIEWERS: I have a question about this item from the design doc: “Unmapped tenant discriminator columns will require EclipseLink to populate the row with the tenant

discriminator column’s associated context property value. See Core section below.” I am confused by this. There is also the item: “On persist, the value of a mapped tenant discriminator column mapping is populated from its associated

context property. “ It sounds like these two might be saying the same thing for mapped and unmapped. What am I missing?

Using Single-Table Multi-Tenancy in an Inheritence Hierarchy

Inheritance strategies are configured by specifying the inheritance type (see @inheritence in

javax.persistence). Single-table multi-tenancy can be used in an inheritance hierarchy, as follows:

  • Multi-tenant metadata can only be applied at the root level of the inheritance hierarchy when using a

SINGLE_TABLE or JOINED inheritance strategy.

  • It is possible to specify multi-tenant metadata within a TABLE_PER_CLASS inheritance hierarchy.

REVIEWERS: How? Is it that the metadata can be applied somewhere down the hierarchy?

Annotation Examples

The following example defines a single discriminator tenant column:   @Entity @Table(name = "CUSTOMER") @Multitenant @TenantDiscriminatorColumn(name = "TENANT",

    contextProperty = "multi-tenant.id")

public Customer() {

 ...

}   The following example defines multiple tenant discriminator columns using multiple tables:   @Entity @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() {

 ...

}   The following example defines a tenant discriminator column mapped as part of the primary key on the

database:   @Entity @Table(name = "ADDRESS") @Multitenant @TenantDiscriminatorColumn(name = "TENANT", contextProperty = "tenant.id",

   primaryKey = true)

public Address() {

 ...

}   The following example defines a mapped tenant discriminator column:   @Entity @Table(name = "Player") @Multitenant @TenantDiscriminatorColumn(name = "AGE",

 contextProperty = "tenant.age")

public Player() {

 ...

@Basic @Column(name="AGE", insertable="false", updatable="false") public int age; }

Specifying Metadata Using XML

You can also use XML to configure single-table multi-tenancy, as an alternative to or in addition to

using annotations. Use the following elements in the eclipselink-orm.xml file: <multitenant> <multitenant-type> <tenant-discriminator-column>   For the EclipseLink schemas, see http://wiki.eclipse.org/EclipseLink/XSDs. REVIEWERS: The design doc makes it look like you don’t have to specify <multitenant-type> when using XML.

Is that correct? XML Examples The following example defines a single tenant discriminator column: <entity class="model.Customer">

 <multitenant>
   <tenant-discriminator-column 
     name="TENANT” 

context-property="multi-tenant.id"/>

 </multitenant>
 <table name="CUSTOMER"/>
 ...

</entity>     The following example defines multiple tenant discriminator columns using multiple tables:   <entity class="model.Employee">

 <multitenant type="SINGLE_TABLE">
   <tenant-discriminator-column name="TENANT_ID" context-property="employee-tenant.id" length="20"/>
   <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>   The following example defines a 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>   The following example defines a 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>  

Property Configuration and Caching Scope

At runtime, context properties can be specified in a persistence unit definition or passed to a

CreateEntityManagerFactory() call, as shown in the following examples.

The order of precedence for tenant discriminator column properties is as follows, listed from highest

priority to lowest: <-- REVIEWERS: is that right? Highest to lowest?

  1. EntityManager
  2. EntityManagerFactory
  3. Application context (when in a Java EE container)

The following example shows a property set in a persistence unit definition in the persistence.xml file:   <persistence-unit name="multi-tenant">

 ...
 <properties>
   <property name="tenant.id" value="707"/>
   ...
 </properties>

</persistence-unit>   The following example shows the property set in code:   HashMap properties = new HashMap(); properties.put("tenant.id", "707"); ... EntityManager em = Persistence.createEntityManagerFactory("multi-tenant",

properties).createEntityManager();

Entity Manager Factory

At the level of the entity manager factory, you must 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). For example:   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();  

Shared Entity Manager Factory

When using a shared entity manager factory, you must set following property to indicate the factory will

be shared: eclipselink.multitenant.tenants-share-cache

When this property is set, all multitenant entities will have a PROTECTED cache setting. Entity Manager

At the level of the entity manager, you must specify caching strategies, because the same server session

can be used for each tenant. For example, you can use an isolation level to ensure no shared tenant

information exists in the L2 cache. These settings are set when creating the entity manager factory. For

example: 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); ... Swapping tenant ID during a live EntityManager is not allowed.

Defining Persistence Unit and Entity Mappings Defaults

You can define single-table multi-tenancy for specific entities and mapped superclasses, as described

above. In addition, you can define the metadata at higher levels in the eclipselink-orm.xml file, to

provide defaults. Standard JPA metadata defaulting and overriding rules apply. The elements used for

specifying these defaults are:

  • <persistence-unit-defaults>
  • <entity-mappings>

Persistence Unit Defaults

You can specify default tenant discriminator column metadata using the <persistence-unit-defaults>

element and its <tenant-discriminator-column> subelement. When defined at this level, it applies to all

entities of the persistence unit that specify the SINGLE_TABLE multi-tenant type, excluding those that

specify their own tenant discriminator metadata. For example: REVIWERS: I came up with this example myself. Is it correct? If not, please advise.

<persistence-unit-metadata>

 <persistence-unit-defaults>
 ...
   <tenant-discriminator-column 
     name="TENANT” 

context-property="multi-tenant.id"/>

   </multitenant>
 </persistence-unit-defaults>

</persistence-unit-metadata>   Note: With no defaults, an entity not marked with multi-tenant metadata will not use any multi-tenancy

strategy.

Entity Mappings Defaults

You can also specify tenant discriminator column metadata using <entity-mappings> element. This overrides

persistence unit defaults (described above) and applies to all entities of the given mapping file that

specify the SINGLE_TABLE multi-tenant type, excluding those entities that specify their own tenant

discriminator metadata. For example, REVIWERS: I came up with this example myself. Is it correct? If not, please advise.

<entity-mappings >

 ...
   <tenant-discriminator-column 
     name="TENANT” 

context-property="multi-tenant.id"/> <entity-mappings >

Example

Defaults always apply even when there are multiple tenant discriminators. This allows you to map several

columns for the same property. For example, the code below defaults the property name to

"eclipselink.tenant-id" and states it should be writing the TENANT column for both the EMPLOYEE and

SALARY table. @Entity @Table(name = "EMPLOYEE") @SecondaryTable(name = "SALARY") @MultiTenant(SINGLE_TABLE) @TenantDiscriminatorColumns({

   @TenantDiscriminatorColumn(name = "TENANT")
   @TenantDiscriminatorColumn(name = "TENANT", table = "SALARY")
 }

) public Employee() {

 ...

}  

Public Default Context Property

The public default context property (eclipselink.tenant-id) definition is available from:

  • org.eclipse.persistence.config.EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT
  • org.eclipse.persistence.config.PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT

For example:

EntityManager em = createEntityManager(MULTI_TENANT_PU); em.setProperty("tenant.id", "707"); em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "707");

Support for Tenant Discriminator Columns through Entity Manager Operations and Querying The tenant discriminator column and value are supported through the following entity manager operations:

  • persist()
  • find()
  • refresh()

They are also supported through the following queries:

  • Named queries
  • Update all
  • Delete all

REVIEWERS: Do you think any examples would be appropriate here? If so, please provide. Note: EclipseLink does not support multi-tenancy through named native queries. If you want to use named

native queries in a multi-tenant environment, you must handle any multi-tenancy issues directly in the

query. In general, it is best to avoid named native queries in a multi-tenant environment.  

Back to the top