Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "EclipseLink/Development/Indigo/Multi-Tenancy"

(Configuration)
Line 14: Line 14:
 
#* Augment database queries to limit query results to the tenant identifier values provided as property values
 
#* Augment database queries to limit query results to the tenant identifier values provided as property values
 
#* Ensure all INSERT, UPDATE, DELETE operations populate and limit their effect to the defined tenant identifiers
 
#* Ensure all INSERT, UPDATE, DELETE operations populate and limit their effect to the defined tenant identifiers
# Support accessing shared data at eithe rthe EntityManagerFactory or EntityManager
+
# Support accessing shared data at either the EntityManagerFactory or EntityManager
 
#* When using EMF the underlying cache must be unque to the provided tenant identifiers
 
#* When using EMF the underlying cache must be unque to the provided tenant identifiers
 
# Support the tenant identifier columns being:
 
# Support the tenant identifier columns being:
Line 20: Line 20:
 
#* mapped  
 
#* mapped  
 
#* part of the identifier of the entity (mapped)
 
#* part of the identifier of the entity (mapped)
 +
# Support schema generation of the tenant identifier columns. Default type will be based on any mapping if available otherwise it will be assumed to be a string and overriden with the column's definition
  
 
=== Configuration ===
 
=== Configuration ===

Revision as of 14:23, 14 February 2011

Bug ??????

Multi-Tenancy

The goal of this feature is to allow multiple application tenants to share the same schema using tenant identifying column(s).

Requirements

  1. Support configuration of shared multi-tenant entity types using a EclipseLink specific annotations or eclispelink-orm.xml with the XML overriding the annotation.
    • Augment database queries to limit query results to the tenant identifier values provided as property values
    • Ensure all INSERT, UPDATE, DELETE operations populate and limit their effect to the defined tenant identifiers
  2. Support accessing shared data at either the EntityManagerFactory or EntityManager
    • When using EMF the underlying cache must be unque to the provided tenant identifiers
  3. Support the tenant identifier columns being:
    • un-mapped
    • mapped
    • part of the identifier of the entity (mapped)
  4. Support schema generation of the tenant identifier columns. Default type will be based on any mapping if available otherwise it will be assumed to be a string and overriden with the column's definition

Configuration

With this new feature developers will be able to enable shared tenant table(s) usage at the entity level using one or more columns associated with persistence unit or context property values that must be provided.

Annotation

@TenantShared(
   @TenantId(property="tenant-id", column=@Column(name="TENANT_ID"))
)

eclipselink-orm.xml

<entity class="model.Employee">
    <table name="EMPLOYEE" />
    <tenant-shared>
        <tenant-id property="tenant-id"><column name="TENANT_ID"/></tenant-id>
    </tenant-shared>

Defaults

  • The column name if unspecified will be the property name converted into a column name using standard JPA String-> column name translations
  • The tenant-identifier column is assumed to exist on the primary table. If using secondary tables the @Column/<column> must specify the table if it is not on the primary.

It is not expected for the following tables (which refers back to their related entity through a primary key association):

  1. @CollectionTable
  2. @JoinTable

NOTE: This assumes id generation is shared across persistence units. Otherwise, in a multi-tenant environment, the tenant id becomes part of the primary key and all tables must then have a tenant id (which becomes another join column on the relation tables).

When using DDL generation, the user need not worry about this. The DDL generation framework will be responsible for ensuring all necessary tables have a tenant id column.

Persistence.xml configuration

The properties may be includes within a peristence unit definition in the persistence.xml file.

<persistence-unit name="multi-tenant-707">
  <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
  <class>org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Boss</class>
  <class>org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Capo</class>
  <class>org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Contract</class>
  <class>org.eclipse.persistence.testing.models.jpa.advanced.multitenant.MafiaFamily</class>
  <class>org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Mafioso</class>
  <class>org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Soldier</class>
  <class>org.eclipse.persistence.testing.models.jpa.advanced.multitenant.Underboss</class>
  <exclude-unlisted-classes>true</exclude-unlisted-classes>
  <properties>
    <property name="eclipselink.multi-tenant.id" value="707"/>
    <property name="eclipselink.logging.level" value="FINEST"/>
    <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
    <property name="eclipselink.create-ddl-jdbc-file-name" value="createMafia707_ddlGeneration.jdbc"/>
    <property name="eclipselink.drop-ddl-jdbc-file-name" value="dropMafia707_ddlGeneration.jdbc"/>
    <property name="eclipselink.ddl-generation.output-mode" value="sql-script"/>
  </properties>
</persistence-unit>

CreateEntityManagerFactory configuration

The properties may alternatively be configured in code as follows:

HashMap properties = new HashMap();
properties.put("eclipselink.multi-tenant.id", "007");
properties.put("eclipselink.logging.level", "FINEST");
properties.put("javax.persistence.jdbc.driver", "oracle.jdbc.OracleDriver");
properties.put("javax.persistence.jdbc.url", "...");
properties.put("javax.persistence.jdbc.user", "...");
properties.put("javax.persistence.jdbc.password", "...");
properties.put("eclipselink.ddl-generation", "drop-and-create-tables");
properties.put("eclipselink.create-ddl-jdbc-file-name", "createMafia007_ddlGeneration.jdbc");
properties.put("eclipselink.drop-ddl-jdbc-file-name", "dropMafia007_ddlGeneration.jdbc");
properties.put("eclipselink.ddl-generation.output-mode", "sql-script");
        
EntityManager em = Persistence.createEntityManagerFactory("puName007", properties).createEntityManager();

Metadata

To further configure the tenant id field, we could provide and allow the following metadata at the @Entity and @MappedSuperclass level.

At the @MappedSuperclass level, the tenant column would apply to all child entities unless they provide their own. Would also be valid to have different tenant columns in a joined inheritance hierarchy.

Annotation
@Target({TYPE}) 
@Retention(RUNTIME)
public @interface @TenantColumn{

    /**
     * (Optional) The name of the column. Defaults to 
     * the property or field name.
     */
    String name() default "";

    /**
     * (Optional) The SQL fragment that is used when 
     * generating the DDL for the column.
     * <p> Defaults to the generated SQL to create a
     * column of the inferred type.
     */
    String columnDefinition() default "";

    /**
     * (Optional) The column length. (Applies only if a
     * string-valued column is used.)
     */
    int length() default 255;

    /**
     * (Optional) The precision for a decimal (exact numeric) 
     * column. (Applies only if a decimal column is used.)
     * Value must be set by developer if used when generating 
     * the DDL for the column.
     */
    int precision() default 0;

    /**
     * (Optional) The scale for a decimal (exact numeric) column. 
     * (Applies only if a decimal column is used.)
     */
    int scale() default 0;
}
XML
  <xsd:complexType name="tenant-column">
    <xsd:annotation>
      <xsd:documentation>

        ...

      </xsd:documentation>
    </xsd:annotation>
    <xsd:attribute name="name" type="xsd:string"/>
    <xsd:attribute name="column-definition" type="xsd:string"/>
    <xsd:attribute name="length" type="xsd:int"/>
    <xsd:attribute name="precision" type="xsd:int"/>
    <xsd:attribute name="scale" type="xsd:int"/>
  </xsd:complexType>

Core

The tenant id field(s) (multiple if using secondary tables) will be initialized during the pre-initialization of each descriptor of the persistence unit.

Those fields will then be applied in two places.

  1. We will leverage the current additional join expression from the DescriptorQueryManager to filter tenants. This is similar to the Additional Criteria feature. During postInitialization of the descriptor query manager after we had appended the additional criteria (if there is some), we will append the tenant id field and its value to the additional join expression.
  2. For inserts, we will append the tenant id column and value when building the row representation of an object. This is done in the following methods from ObjectBuilder (Note: similar thing is done for the the handling of the discriminiator column within an inheritance hierarchy)
    1. buildRow
    2. buildRowForShallowInsert
    3. buildRowForUpdate
    4. buildRowWithChangeSet
    5. buildTemplateInsertRow

Querying

The tenant id column and value will be supported through the following entity manager operations:

  • persist
  • find
  • refresh

And the following queries:

  • named queries

NOTE: EclipseLink will not modify, therefore, support multi-tenancy through named native queries. When using these types of queries within a multi-tenant environment, the user will need to be aware and handle any multi-tenancy issues themselves directly in their native query. To all intent and purpose, named native queries should be avoided in a multi-tenant environment.

DDL generation

DDL generation will need to support the generation of tenant id columns. The DDL generation of fields is based off the descriptor's fields. During pre-initialization we therefore need to ensure that our tenant id fields are built and added to this list. This should be done after the descriptor table initialization (including inheritance hierarchies) has been preformed.

if (session.hasTenantId()) {
  for (DatabaseTable table : getTables()) {
    DatabaseField tenantIdField = session.getTenantIdField();
    tenantIdField.setTable(table);
    getFields().add(buildField(tenantIdField));    
  } 
}

Back to the top