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"

(Metadata Processing Warnings and Exceptions)
(More Examples)
Line 130: Line 130:
 
* ??
 
* ??
  
=== More Examples ===
 
  
==== Annotation examples ====
 
<source lang="java">
 
 
/** Single discriminator tenant column **/
 
 
@Entity
 
@Table(name = "CUSTOMER")
 
@Multitenant
 
@TenantDescriminatorColumn(name = "TENANT", contextProperty = "multi-tenant.id")
 
public Customer() {
 
  ...
 
}
 
 
/** 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() {
 
  ...
 
}
 
 
/** 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() {
 
  ...
 
}
 
 
/** 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;
 
}
 
 
</source>
 
 
==== XML examples ====
 
 
<source lang="xml">
 
 
<!-- Single tenant discriminator column -->
 
 
<entity class="model.Customer">
 
  <multitenant>
 
    <tenant-discriminator-column name="TENANT context-property="multi-tenant.id""/>
 
  </multitenant>
 
  <table name="CUSTOMER"/>
 
  ...
 
</entity>
 
 
<!-- 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>
 
 
<!-- 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>
 
 
</source>
 
  
 
== Property configuration and caching scope ==
 
== Property configuration and caching scope ==

Revision as of 11:35, 27 March 2012

This feature will be a continuation of the multitenant feature that currently offers a SINGLE_TABLE type only. [1]

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.

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.
    • Augment database queries to limit query results to the tenant per table
    • Ensure all INSERT, UPDATE, DELETE operations populate and limit their effect to the defined table per tenant
  2. 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)
  3. Schema generation??? TBD

Metadata Configuration

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.

  • The TABLE_PER_TENANT states that the table(s) (Table and SecondaryTable) for the given entity are individual tenant tables based on the user.
  • A TABLE_PER_TENANT type is used in conjunction with a table per tenant property definition to identify the user.

Important notes

  • 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)
public class Employee {
    ...
}

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

The following new EclipseLink metadata will be added.

Annotation usage

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

This means we will simple extend the MultitenantTenant enum to include TABLE_PER_TENANT.

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.
     */
    VPD
 
    /**
     * Specifies that different tables are used for each tenant. The table scan be uniquely
     * identified by name, schema/tablespace.
     */
    TABLE_PER_TENANT 
}

XML usage

The multitenant-type XML element will be expanded to include the new type.

<xsd:simpleType name="multitenant-type">
  <xsd:annotation>
    <xsd:documentation>
      ...
    </xsd:documentation>
  </xsd:annotation>
  <xsd:restriction base="xsd:token">
    <xsd:enumeration value="SINGLE_TABLE"/>
    <xsd:enumeration value="VPD"/>
    <xsd:enumeration value="TABLE_PER_TENANT"/>
  </xsd:restriction>
</xsd:simpleType>

Metadata Processing Warnings and Exceptions

  •  ??


Property configuration and caching scope

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)
<persistence-unit name="multi-tenant">
  ...
  <properties>
    <property name="tenant.id" value="707"/>
    ...
  </properties>
</persistence-unit>

Or alternatively (and most likely preferred) in code as follows:

HashMap properties = new HashMap();
properties.put("tenant.id", "707");
...     
EntityManager em = Persistence.createEntityManagerFactory("multi-tenant", properties).createEntityManager();

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).

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

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.

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);
...

Core

The tenant dsicriminator column(s) will be initialized during the pre-initialization of each descriptor of the persistence unit.

Those columns will then be applied in two places.

  1. We will leverage the current additional join expression from the DescriptorQueryManager to filter tenants. This is similar to the 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.
    1. The tenant discriminator column value(s) will be added as arguments when issuing queries.
  2. 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)
    1. buildRow
    2. buildRowForShallowInsert
    3. buildRowForUpdate
    4. buildRowWithChangeSet
    5. 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.
  3. For reading, to enable the additional criteria (tenant discriminator columns) we will need to modify the ReadObjectQuery and ReadAllQuery slightly in the prepareForExecution method.
    1. 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):

  1. @CollectionTable
  2. @JoinTable
  3. JOINED inheritance hierarchy tables
  4. SINGLE_TABLE inheritance hierarchy

NOTE: Id generation is shared across persistence units (see future section below).

Core/Runtime Exceptions

  • An exception will be thrown when a named tenant property can not be found.

Properties

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

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

Java example

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

Querying

The tenant discriminator column and value will be supported 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 themselves directly in their native query. To all intent and purpose, named native queries should be avoided in a multi-tenant environment.

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.

if (hasTenantDiscriminatorFields()) {
    for (DatabaseField discriminatorField : tenantDiscriminatorFields.keySet()) {
        getFields().add(buildField(discriminatorField));
    }
}

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:

isPKField = isPKField || dbField.isPrimaryKey();

Open/Future items

  1. Bug 355458: Provide admin user access data from multiple tenants
  2. Tenant column when part of the entity identifier
    1. Incorporate sequence generators
  3. 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.

Back to the top