Jump to: navigation, search

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

Line 1: Line 1:
 
<div style="float:right;width:300px">
 
<div style="float:right;width:300px">
{{EclipseLink_DocWiki|link=EclipseLink/UserGuide/JPA/Advanced_JPA_Development/Single-Table_Multi-Tenancy}}
+
<div style="float:right;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 |'''User's Guide''']]
 +
</div>
 
__TOC__  
 
__TOC__  
 
</div>  
 
</div>  

Revision as of 13:43, 21 June 2011

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