With single-table multi-tenancy, any table (Table
or SecondaryTable
) to which an entity or mapped superclass maps can include rows for multiple tenants. Access to tenant-specific rows is restricted to the specified tenant.
Tenant-specific rows are associated with the tenant by using one or more tenant discriminator columns. 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, and multiple tenants can share all the tables. |
The following tasks provide instructions for using single-table multi-tenancy:
To implement and use single-table multi-tenancy, you need:
EclipseLink 2.4 or later.
Download EclipseLink from http://www.eclipse.org/eclipselink/downloads/
.
Any compliant Java Database Connectivity (JDBC) database, including Oracle Database, Oracle Database Express Edition (Oracle Database XE), or MySQL. These instructions are based on Oracle Database XE 11g Release 2.
For the certification matrix, see
Single-table multi-tenancy can be enabled declaratively using the @Multitenant
annotation, in an Object Relational Mapping (ORM) XML file using the <multitenant>
element, or by using annotations and XML together.
To use the @Multitenant
annotation, include it with an @Entity
or @MappedSuperclass
annotation. For example:
@Entity @Table(name=”EMP”) @Multitenant(SINGLE_TABLE) public class Employee { }
Note: Single-table is the default multi-tenancy type, so |
Note: The |
Discriminator columns are used together with an associated application context to indicate which rows in a table an application tenant can access.
Tenant discriminator columns can be specified declaratively using the @TenantDiscriminatorColumn
annotation or in an object-relational (ORM) XML file using the <tenant-discriminator-column>
element.
The following characteristics apply to discriminator columns:
Tenant discriminator column(s) must always be used with @Multitenant
(or <multitenant>
in the ORM XML file). You cannot specify the tenant discriminator column(s) only.
The tenant discriminator column is assumed to be on the primary table unless another table is explicitly specified.
On persist, the values of tenant discriminator columns are populated from their associated context properties.
When a multi-tenant entity is specified, the tenant discriminator column can default. Its default values are:
Name = TENANT_ID
(the database column name)
Context property = eclipselink.tenant.id
(the context property used to populate the database column)
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 the number of tenant discriminator columns an application can define.
Any name can be used for a discriminator column.
Generated schemas 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.
Both mapped and unmapped properties are used to form the additional criteria when issuing a SELECT query.
To use the @TenantDiscriminatorColumn
annotation, include it with @Multitenant
annotation on an entity or mapped superclass, and optionally include the name
and contextProperty
attributes. If you do not specify these attributes, the defaults name = "TENANT-ID"
and contextProperty = "eclipselink.tenant-id"
are used.
For example:
@Entity @Multitenant(SINGLE_TABLE) @TenantDiscriminatorColumn(name = "TENANT", contextProperty = "multitenant.id") public class Employee { }
To specify multiple tenant discriminator columns, include multiple @TenantDiscriminatorColumn
annotations within the @TenantDiscriminatorColumns
annotation, and include the table where the column is located if it is not located on the primary table. For example:
@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() { ... }
To use the <tenant-discriminator-column>
element, include the element within a <multitenant>
element and optionally include the name
and context-property
attributes. If you do not specify these attributes, the defaults name = "TENANT-ID"
and contextProperty = "eclipselink.tenant-id"
are used.
For example:
<entity class="model.Employee"> <multitenant> <tenant-discriminator-column name="TENANT" context-property="multitenant.id"/> </multitenant> ... </entity>
To specify multiple columns, include additional <tenant-discriminator-column>
elements, and include the table where the column is located if it is not located on the primary table. For example:
<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 columns can be mapped to a primary key or to another column. The following example maps the tenant discriminator column to the primary key on the table during DDL generation:
@Entity @Table(name = "ADDRESS") @Multitenant @TenantDiscriminatorColumn(name = "TENANT", contextProperty = "tenant.id", primaryKey = true) public Address() { ... }
The following example uses the ORM XML file to map the tenant discriminator column to a primary key:
<entity class="model.Address"> <multitenant> <tenant-discriminator-column name="TENANT" context-property="multitenant.id" primary-key="true"/> </multitenant> <table name="ADDRESS"/> ... </entity>
The following example maps the tenant discriminator column to another column named AGE
:
@Entity @Table(name = "Player") @Multitenant @TenantDiscriminatorColumn(name = "AGE", contextProperty = "tenant.age") public Player() { ... @Basic @Column(name="AGE", insertable="false", updatable="false") public int age; }
The following example uses the ORM XML file to map the tenant discriminator column to another column named AGE
:
<entity class="model.Player"> <multitenant> <tenant-discriminator-column name="AGE" context-property="tenant.age"/> </multitenant> <table name="PLAYER"/> ... <attributes> <basic name="age" insertable="false" updatable="false"> <column name="AGE"/> </basic> ... </attributes> ... </entity>
In addition to configuring discriminator columns at the entity and mapped superclass levels, you can also configure them at the persistence-unit-defaults
and entity-mappings
levels to provide defaults. Defining the metadata at the these levels follows similar JPA metadata defaulting and overriding rules.
Specify default tenant discriminator column metadata at the persistence-unit-defaults
level in the ORM XML file. When defined at this level, the defaults 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. For example:
<persistence-unit-metadata> <persistence-unit-defaults> <tenant-discriminator-column name="TENANT_ID" context-property="tenant.id"/> </persistence-unit-defaults> </persistence-unit-metadata>
You can also specify tenant discriminator column metadata at the entity-mappings
level in the ORM XML file. A setting at this level overrides a persistence unit default and applies to all entities with a multi-tenant type of SINGLE_TABLE
of the mapping file, minus those that specify their own tenant discriminator metadata. For example:
<entity-mappings> ... ... <tenant-discriminator-column name="TENANT_ID" context-property="tenant.id"/> ... </entity-mappings>
Runtime context properties are used in conjunction with the multi-tenancy configuration on an entity (or mapped superclass) to implement the multi-tenancy strategy. For example, the tenant ID assigned to a tenant discriminator column for an entity is used at runtime (via an entity manager) to restrict access to data, based on that tenant's ownership of (or access to) the rows and tables of the database.
At runtime, multi-tenancy properties can be specified in a persistence unit definition or passed to a create entity manager factory call.
The order of precedence for tenant discriminator column properties is as follows:
EntityManager
EntityManagerFactory
Application context (when in a Jakarta EE container)
For example, to set the configuration on a persistence unit in persistence.xml
:
<persistence-unit name="multitenant"> ... <properties> <property name="tenant.id" value="707"/> ... </properties> </persistence-unit>
Alternatively, to set the properties programmatically:
HashMap properties = new HashMap(); properties.put("tenant.id", "707"); ... EntityManager em = Persistence.createEntityManagerFactory("multi-tenant", properties).createEntityManager();
Note: Swapping tenant IDs during a live |
By default, tenants share the entity manager factory. A single application instance with a shared EntityManagerFactory
for a persistence unit can be responsible for handling requests from multiple tenants.
The following example shows a shared entity manager factory configuration:
EntityManager em = createEntityManager(MULTI_TENANT_PU); em.getTransaction().begin(); em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "my_id");
When using a shared entity manager factory, the L2 cache is by default not shared, and therefore multi-tenant entities have an ISOLATED
cache setting.
To share the cache, set the eclipselink.multitenant.tenants-share-cache
property to true
. This results in multi-tenant entities having a PROTECTED cache setting.
Caution: Queries that use the cache may return data from other tenants when using the |
To create an entity manager factory that is not shared, set the eclipselink.multitenant.tenants-share-emf
property to false
.
When the entity manager factory is not shared, you must use the eclipselink.session-name
property to provide a unique session name, as shown in the following example. This ensures that a unique server session and cache are provided for each tenant. This provides full caching capabilities. For example,
HashMap properties = new HashMap(); properties.put("tenant.id", "707"); properties.put("eclipselink.session-name", "multi-tenant-707"); ... EntityManager em = Persistence.createEntityManagerFactory("multitenant", properties).createEntityManager();
Another example:
HashMap properties = new HashMap(); properties.put(PersistenceUnitProperties.MULTITENANT_SHARED_EMF, "false"); properties.put(PersistenceUnitProperties.SESSION_NAME, "non-shared-emf-for-this-emp"); properties.put(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, "this-emp"); ... EntityManager em = Persistence.createEntityManagerFactory("multi-tenant-pu", properties).createEntityManager();
An example in the persistence unit definition:
<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-this-emp"/> <property name="eclipselink.tenant-id" value="this-emp"/> ... </properties> </persistence-unit>
When configuring properties at the level of the entity manager, you must specify the caching strategies, because the same server session can be used for each tenant. For example, you can set up an isolation level (L1 cache) to ensure no shared tenant information exists in the L2 cache. These settings are set when creating the entity manager factory.
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("multitenant", cacheProperties).createEntityManager(tenantProperties); ...
The tenant discriminator column is used at runtime through entity manager operations and querying. The tenant discriminator column and value are supported through the following entity manager operations:
persist()
find()
refresh()
The tenant discriminator column and value are supported through the following queries:
Named queries
Update all
Delete all
Note: Multi-tenancy is not supported through named native queries. To use named native queries in a multi-tenant environment, manually handle any multi-tenancy issues directly in the query. In general, it is best to avoid named native queries in a multi-tenant environment. |
Inheritance strategies are configured by specifying the inheritance type (@jakarta.persistence.Inheritance
). 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.