Jump to: navigation, search

Difference between revisions of "EclipseLink/Examples/JPA/Multitenant"

(Persistence Usage for Multiple Tenants)
 
(27 intermediate revisions by 3 users not shown)
Line 1: Line 1:
EclipseLink (as of 2.3) supports a shared (striped) multitenant table using a tenant discriminator column(s).
+
<div style="float:right;width:300px">
 +
<div style="padding:0.5em;margin:0.5em 0.5em 1em 1em;border:1px solid #D9D9D9;background:#EFEFEF;-moz-border-radius:4px;">[[Image:Elug_doc_wiki.png|link= ]] [[EclipseLink/UserGuide/JPA/Advanced_JPA_Development/Single-Table_Multi-Tenancy |'''Documentation''']]
 +
</div>
 +
__TOC__
 +
</div>
 +
= EclipseLink Multitenant Examples  =
  
EclipseLink supports multitenant entities using its <code>@Multitenant</code> annotation or <code><multitenant></code> xml element. The <code>@Multitenant</code> annotation can be used on an <code>@Entity</code> or <code>@MappedSuperclass</code> and is used in conjunction with the <code>@TenantDiscriminatorColumn</code> or <code><tenant-discriminator-column></code> xml element.
+
EclipseLink (as of 2.3.0 - Indigo) supports shared multitenant tables using tenant discriminator column(s), allowing an application to be re-used for multiple tenants and have all their data co-located. All tenants share the same schema without being aware of one another and can use non-multitenant entity types as per usual. This new functionality is flexible enough to allow for its usage at an Entity Manager Factory level or with individual Entity Manager's based on your application needs.  
  
The tenant discriminator column defines the tenant identifying database column and there may be 1 or more such columns. These columns can be unmapped or mapped columns. When mapped, they must be marked as read-only. See the annotation and xml examples below.
+
Support for multitenant entities is done though the usage of the <code>@Multitenant</code> annotation or <code>&lt;multitenant&gt;</code> xml element configured in your [[EclipseLink/Examples/JPA/EclipseLink-ORM.XML|eclipselink-orm.xml]] mapping file. The <code>@Multitenant</code> annotation can be used on an <code>@Entity</code> or <code>@MappedSuperclass</code> and is used in conjunction with the <code>@TenantDiscriminatorColumn</code> or <code>&lt;tenant-discriminator-column&gt;</code> xml element.  
  
When multitenancy is specified, the tenant discriminator column can default. Its default values are:
+
The tenant discriminator column defines the tenant identifying database column and there may be 1 or more such columns. These columns can be unmapped or mapped. When mapped, they must be marked as read-only. See the annotation and xml examples to follow.  
  
*name = <code>TENANT_ID</code>
+
When a multitenant entity is specified, the tenant discriminator column can default. Its default values are:
*context property = <code>tenant.id</code>
+
  
Note, through annotations, specifying only a tenant discriminator column itself does not enable a multitenant entity. At a minimum, the <code>@Multitenant</code> must also be specified. Meaning the minimal configuration is:
+
*name = <code>TENANT_ID</code> (the database column name)
 +
*context property = <code>tenant.id</code> (the context property used to populate the database column)
 +
 
 +
The context property is a value that is required at runtime in order to acces sthe specific rows for a tenant. This value is configured at the persistenec unit or persistence context (see usages) and if not specified a runtime exception will be thrown when attempting to query or modify a multitenant entity type.
 +
 
 +
== Persistence Usage for Multiple Tenants  ==
 +
 
 +
There are multiple usage options available for how an EclipseLink JPA persistence unit can be used in an application with <code>@Multitenant</code> entity types. Since different tenants will have access to only its rows the persistence layer must be configured so that entities from different tenants do not end up in the same cache.
 +
 
 +
These architetcures with usage notes include:
 +
 
 +
*'''Dedicated Persistence Unit''': In this usage there is a persistence unit defined per tenant and the application must request the correct PersistenceContext or PersistenceUnit for its tenant. This can be used through container managed or application bootstrapped JPA.
 +
 
 +
<source lang="xml">
 +
<persistence-unit name="mysports-OSL">
 +
  ...
 +
  <properties>
 +
    <property name="eclipselink.tenant-id" value="OSL"/>
 +
    ...
 +
  </properties>
 +
</persistence-unit>
 +
</source>
 +
 
 +
*'''Persistence Context per Tenant''': Using a single persistence unit definition in the persistence.xml and a shared persistence unit (EntityManagerFactory and cache) the tenant context is specified per persistence Context (EntityManager) at runtime using the [http://www.eclipse.org/eclipselink/api/2.3/javax/persistence/EntityManagerFactory.html#createEntityManager%28java.util.Map%29 createEntityManager(Map)] API. This approach can be used with <code>@PersistenceUnit</code> injection but '''not''' with container managed <code>@PersistenceContext</code> injection.
 +
**When using this architecture there is a shared cache available for regular entity types but the Multitenant types must be PROTECTED in the cache so the [http://www.eclipse.org/eclipselink/api/2.3/org/eclipse/persistence/config/PersistenceUnitProperties.html#MULTITENANT_SHARED_EMF MULTITENANT_SHARED_EMF] property must be set to '''true'''.
 +
 
 +
<source lang="xml">
 +
<property name="eclipselink.multitenant.tenants-share-cache" value="true" />
 +
</source>
 +
 
 +
*'''Persistence Unit per Tenant''': In this architecture there is a single persistence unit defined in the persistence.xml and through use of the application bootstrap API (no container injection supported) new persistence contexts with their own caches are created per tenant.
 +
**The <code>eclipselink.session-name</code> ([http://www.eclipse.org/eclipselink/api/2.3/org/eclipse/persistence/config/PersistenceUnitProperties.html#SESSION_NAME SESSION_NAME]) property must be provided to ensure a unique server session (and cache) is provided for each tenant.
 +
 
 +
=== Usage Summary ===
 +
{|{{BMTableStyle}}
 +
|-{{BMTHStyle}}
 +
! Usage
 +
! @PersistenceContext<br/>EntityManager Injection
 +
! @PersistenceUnit<br/>EntityManagerFactory Injection
 +
! Persistence.createEntityManagerFactory<br/>Application Bootstrap API
 +
|-
 +
| Dedicated
 +
| Yes
 +
| Yes
 +
| Yes
 +
|-
 +
| Persistence Context per Tenant
 +
| No
 +
| Yes
 +
| Yes
 +
|-
 +
| Persistence Unit per Tenant
 +
| No
 +
| No
 +
| Yes
 +
|}
 +
 
 +
== Simple Example  ==
 +
 
 +
Note, through annotations, specifying only a tenant discriminator column itself does not enable a multitenant entity. At a minimum, the <code>@Multitenant</code> must also be specified. Meaning the minimal configuration is:  
  
 
<source lang="java">
 
<source lang="java">
 
@Entity
 
@Entity
@Table(name="EMP")
 
 
@Multitenant
 
@Multitenant
public Employee() {
+
public class Player  {
  ...
+
 
}
 
}
</source>
+
</source>  
 +
 
 +
This configuration means that the Player entity type has rows for multiple tenants stored in its default '''PLAYER''' table and that the default '''TENANT_ID''' column is used as a discriminator along with the default context property '''eclipselink.tenant-id'''.
 +
 
 +
Assuming this application wants to use a shared <code>EntityManagerFactory</code> and have the <code>EntityManager</code> be tenant specific then it would be used like:
 +
 
 +
<source lang="java">
 +
Map<String, Object> emProperties = new HashMap<String, Object>();
 +
 
 +
emProperties.set("eclipselink.tenant-id", "HTHL");
 +
 
 +
EntityManager em = emf.createEntityManager(emProperties);
 +
</source>
 +
 
 +
== Additional Examples  ==
  
The following examples outline other possible configurations using annotations and XML.
+
The following examples outline other possible configurations using annotations and XML.  
  
==== Annotation examples ====
 
 
<source lang="java">
 
<source lang="java">
  
Line 74: Line 148:
 
   public int age;
 
   public int age;
 
}
 
}
</source>
+
</source>  
 
+
==== XML examples ====
+
  
 
<source lang="xml">
 
<source lang="xml">
Line 128: Line 200:
 
   ...
 
   ...
 
</entity>
 
</entity>
 +
</source>
  
</source>
+
At runtime, the context property configuration can be specified via a persistence unit definition, passed to a create entity manager factory call or set on individual entity managers.  
 
+
== 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)
+
  
 
<source lang="xml">
 
<source lang="xml">
Line 148: Line 212:
 
   </properties>
 
   </properties>
 
</persistence-unit>
 
</persistence-unit>
</source>
+
</source>  
  
Or alternatively (and most likely preferred) in code as follows:
+
Or alternatively in code as follows:  
  
 
<source lang="java">
 
<source lang="java">
 
HashMap properties = new HashMap();
 
HashMap properties = new HashMap();
properties.put("tenant.id", "707");
+
properties.put(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, "707");  
...   
+
EntityManager em = Persistence.createEntityManagerFactory("multi-tenant-pu", properties).createEntityManager();
EntityManager em = Persistence.createEntityManagerFactory("multi-tenant", properties).createEntityManager();
+
</source>  
</source>
+
  
==== Entity Manager Factory ====
+
An entity Manager property definition would be as follows:
 
+
At this level, users will be required to provide a unique session name through the "eclipselink.session-name" property to ensure a unique server session (and cache) is provided for each tenant. This allows for user defined properties (without any prefixing). In further iterations we will look to augment the session name automatically for the user based on their tenant property values (or something thereof).
+
  
 
<source lang="java">
 
<source lang="java">
HashMap properties = new HashMap();
+
EntityManager em = Persistence.createEntityManagerFactory("multi-tenant-pu").createEntityManager();
properties.put("tenant.id", "707");
+
em.beginTransaction();
properties.put("eclipselink.session-name", "multi-tenant-707");
+
em.setProperty("other.tenant.id.property", "707");
...   
+
em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "707");
EntityManager em = Persistence.createEntityManagerFactory("multi-tenant", properties).createEntityManager();
+
</source>
+
 
+
===== Shared Entity Manager Factory =====
+
 
+
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:
+
 
+
<source lang="XML">
+
eclipselink.multitenant.tenants-share-cache
+
</source>
+
 
+
When this property is set, all multitenant entities will have a PROTECTED cache setting.
+
 
+
==== Entity Manager ====
+
 
+
At this level, users will be required to specify the caching strategies as the same server session can be employed for each tenant. Users may decide to us an isolation level here etc to ensure no 'shared' tenant information exists in the L2 cache. These settings are set when creating the entity manager factory.
+
 
+
Swapping tenant id during a live EntityManager is not allowed.
+
 
+
<source lang="java">
+
HashMap tenantProperties = new HashMap();
+
properties.put("tenant.id", "707");
+
 
+
HashMap cacheProperties = new HashMap();
+
properties.put("eclipselink.cache.shared.Employee", "false");
+
properties.put("eclipselink.cache.size.Address", "10");
+
properties.put("eclipselink.cache.type.Contract", "NONE");
+
...   
+
EntityManager em = Persistence.createEntityManagerFactory("multi-tenant", cacheProperties).createEntityManager(tenantProperties);
+
 
...
 
...
 
</source>
 
</source>

Latest revision as of 12:30, 6 September 2012

EclipseLink Multitenant Examples

EclipseLink (as of 2.3.0 - Indigo) supports shared multitenant tables using tenant discriminator column(s), allowing an application to be re-used for multiple tenants and have all their data co-located. All tenants share the same schema without being aware of one another and can use non-multitenant entity types as per usual. This new functionality is flexible enough to allow for its usage at an Entity Manager Factory level or with individual Entity Manager's based on your application needs.

Support for multitenant entities is done though the usage of the @Multitenant annotation or <multitenant> xml element configured in your eclipselink-orm.xml mapping file. The @Multitenant annotation can be used on an @Entity or @MappedSuperclass and is used in conjunction with the @TenantDiscriminatorColumn or <tenant-discriminator-column> xml element.

The tenant discriminator column defines the tenant identifying database column and there may be 1 or more such columns. These columns can be unmapped or mapped. When mapped, they must be marked as read-only. See the annotation and xml examples to follow.

When a multitenant entity is specified, the tenant discriminator column can default. Its default values are:

  • name = TENANT_ID (the database column name)
  • context property = tenant.id (the context property used to populate the database column)

The context property is a value that is required at runtime in order to acces sthe specific rows for a tenant. This value is configured at the persistenec unit or persistence context (see usages) and if not specified a runtime exception will be thrown when attempting to query or modify a multitenant entity type.

Persistence Usage for Multiple Tenants

There are multiple usage options available for how an EclipseLink JPA persistence unit can be used in an application with @Multitenant entity types. Since different tenants will have access to only its rows the persistence layer must be configured so that entities from different tenants do not end up in the same cache.

These architetcures with usage notes include:

  • Dedicated Persistence Unit: In this usage there is a persistence unit defined per tenant and the application must request the correct PersistenceContext or PersistenceUnit for its tenant. This can be used through container managed or application bootstrapped JPA.
<persistence-unit name="mysports-OSL">
  ...
  <properties>
    <property name="eclipselink.tenant-id" value="OSL"/>
    ...
  </properties>
</persistence-unit>
  • Persistence Context per Tenant: Using a single persistence unit definition in the persistence.xml and a shared persistence unit (EntityManagerFactory and cache) the tenant context is specified per persistence Context (EntityManager) at runtime using the createEntityManager(Map) API. This approach can be used with @PersistenceUnit injection but not with container managed @PersistenceContext injection.
    • When using this architecture there is a shared cache available for regular entity types but the Multitenant types must be PROTECTED in the cache so the MULTITENANT_SHARED_EMF property must be set to true.
<property name="eclipselink.multitenant.tenants-share-cache" value="true" />
  • Persistence Unit per Tenant: In this architecture there is a single persistence unit defined in the persistence.xml and through use of the application bootstrap API (no container injection supported) new persistence contexts with their own caches are created per tenant.
    • The eclipselink.session-name (SESSION_NAME) property must be provided to ensure a unique server session (and cache) is provided for each tenant.

Usage Summary

Usage @PersistenceContext
EntityManager Injection
@PersistenceUnit
EntityManagerFactory Injection
Persistence.createEntityManagerFactory
Application Bootstrap API
Dedicated Yes Yes Yes
Persistence Context per Tenant No Yes Yes
Persistence Unit per Tenant No No Yes

Simple Example

Note, through annotations, specifying only a tenant discriminator column itself does not enable a multitenant entity. At a minimum, the @Multitenant must also be specified. Meaning the minimal configuration is:

@Entity
@Multitenant
public class Player  {
}

This configuration means that the Player entity type has rows for multiple tenants stored in its default PLAYER table and that the default TENANT_ID column is used as a discriminator along with the default context property eclipselink.tenant-id.

Assuming this application wants to use a shared EntityManagerFactory and have the EntityManager be tenant specific then it would be used like:

Map<String, Object> emProperties = new HashMap<String, Object>();
 
emProperties.set("eclipselink.tenant-id", "HTHL");
 
EntityManager em = emf.createEntityManager(emProperties);

Additional Examples

The following examples outline other possible configurations using annotations and XML.

/** Single discriminator tenant column **/
 
@Entity
@Table(name = "CUSTOMER")
@Multitenant
@TenantDiscriminatorColumn(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;
}
<!-- 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>

At runtime, the context property configuration can be specified via a persistence unit definition, passed to a create entity manager factory call or set on individual entity managers.

<persistence-unit name="multi-tenant">
  ...
  <properties>
    <property name="tenant.id" value="707"/>
    ...
  </properties>
</persistence-unit>

Or alternatively in code as follows:

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

An entity Manager property definition would be as follows:

EntityManager em = Persistence.createEntityManagerFactory("multi-tenant-pu").createEntityManager();
em.beginTransaction();
em.setProperty("other.tenant.id.property", "707");
em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "707");
...