Jump to: navigation, search

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

Line 1: Line 1:
EclipseLink (as of 2.3) supports shared multitenant tables using tenant discriminator column(s), allowing an existing application to be re-used for new customers or group of users (tenant) and have all their data co-located.
+
EclipseLink (as of 2.3) supports shared multitenant tables using tenant discriminator column(s), allowing an existing application to be re-used for multiple customers (tenants) and have all their data co-located. All tenants share the same schema without being aware of on another.
  
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.
+
This functionality is flexible enough to allow for its usage at an Entity Manager Factory level or to individual Entity Manager levels based on your application needs.
 +
 
 +
Support for multitenant entities is done though the usage of the <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.
  
 
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.
 
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.

Revision as of 14:01, 17 June 2011

EclipseLink (as of 2.3) supports shared multitenant tables using tenant discriminator column(s), allowing an existing application to be re-used for multiple customers (tenants) and have all their data co-located. All tenants share the same schema without being aware of on another.

This functionality is flexible enough to allow for its usage at an Entity Manager Factory level or to individual Entity Manager levels based on your application needs.

Support for multitenant entities is done though the usage of the @Multitenant annotation or <multitenant> xml element. 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)

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
@Table(name="EMP")
@Multitenant
public Employee() {
  ...
}

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

Notes:

  • When a shared persistence unit used, no L2 cache 'striping' is performed, and the eclipselink.multitenant.tenants-share-cache (PersistenceUnitProperties.MULTITENANT_SHARED_EMF) property must be set indicating that all multitenant entities will have a PROTECTED cache setting.
  • When using a non-shared persistence unit, the eclipselink.session-name (PersistenceUnitProperties.SESSION_NAME) property must be provided to ensure a unique server session (and cache) is provided for each tenant.
  • When using Native SQL queries, it is the application's responsibility to ensure that that the tenant discriminator is included in the query.