Jump to: navigation, search

EclipseLink/Examples/JPA/Multitenant

EclipseLink (as of 2.3) supports a shared (striped) multitenant table using a tenant discriminator column(s).

EclipseLink supports multitenant entities using its @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.