Single-Table Multi-Tenancy

This feature is new in EclipseLink 2.3.

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.

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). 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 tables (Table and SecondaryTable), and multiple tenants can share all the tables.

@Multitenant Attributes

Attribute

Description

Default

Required?

value

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.

false

No

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:

Annotate the entity or mapped superclass to use single-table multi-tenancy, using the @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.

Note: The @Table annotation is not required, because the discriminator column is assumed to be on the primary table. However, if the discriminator column is defined on a secondary table, you must identify that table using @SecondaryTable.

Specify the column or columns to be used as the discriminator column, using the @TenantDiscriminatorColumn annotation, for example:

Using Discriminator Columns

On persist, the values of tenant discriminator columns are populated from their associated context properties.

Tenant discriminator columns are application definable. That is, the discriminator column is not tied to a specific column for each shared entity table. You can use TENANT_ID, T_ID, etc.

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:

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.

Both mapped and unmapped properties are used to form the additional criteria when issuing a SELECT query.

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 be applied only at the root level of the inheritance hierarchy when using a SINGLE_TABLE or JOINED inheritance strategy.

You can also specify multi-tenant metadata within a TABLE_PER_CLASS inheritance hierarchy. In this case, every entity has its own table, with all its mapping data (which is not the case with SINGLE_TABLE or JOINED strategies). Consequently, in the TABLE_PER_CLASS strategy, some entities of the hierarchy may be multi-tenant, while others may not be. The other inheritance strategies can only specify multi-tenancy at the root level, because you cannot isolate an entity to a single table to build only its type.

Configuring the 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 that a unique server session (and cache) is provided for each tenant. This allows for user-defined properties (without any prefixing). For example:

Configuring a Shared Entity Manager Factory

When using a shared entity manager factory, you must set the eclipselink.multitenant.tenants-share-cache property to indicate the factory will be shared:

When this property is set, all multitenant entities have a PROTECTED cache setting. At the level of the entity manager, you are responsible for setting 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 second-level (L2) cache. These settings are set when creating the entity manager factory. For example:

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:

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,

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.

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

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.