|
|
(41 intermediate revisions by the same user not shown) |
Line 1: |
Line 1: |
− | = Single-Table Multi-Tenancy =
| |
| | | |
− | The <tt>SINGLE_TABLE</tt> multi-tenant type specifies that any table to which an entity or mapped
| |
− |
| |
− | superclass maps can include rows for multiple tenants. Access to tenant-specific rows is restricted to
| |
− |
| |
− | the tenant.
| |
− |
| |
− | Tenant-specific rows are associated with the tenant by using tenant discriminator columns. The
| |
− |
| |
− | ''discriminator columns'' are used with application context values to limit what a persistence context
| |
− |
| |
− | can access.
| |
− |
| |
− | The results of queries on the mapped tables are limited to the tenant discriminator value(s) provided as
| |
− |
| |
− | property values. This applies to all insert, update, and delete operations on the table. When multi-tenant metadata is applied at the mapped superclass level, it is applied to all subentities
| |
− |
| |
− | unless they specify their own multi-tenant metadata. ''''REVIEWERS: I didn’t include any of the info re: '''
| |
− |
| |
− | homogeneity from the design doc, because I took it to be implementation details that will be pertinent
| |
− |
| |
− | when TABLE_PER_TENANT is available. Therefore, I left it out. Please advise if I should add any back
| |
− |
| |
− | in.'
| |
− |
| |
− | '''Note:''' In the context of single-table multi-tenancy, “single-table” means multiple tenants can share
| |
− |
| |
− | a single table, and each tenant’s data is distinguished from other tenants’ data via the discriminator
| |
− |
| |
− | column(s). This is opposed to a different kind of multi-tenancy where each tenant’s data is stored in
| |
− |
| |
− | separate tables, and the tenant’s data is distinguished from other tenants’ data by identifying the
| |
− |
| |
− | table. It is possible to use multiple tables with single-table multi-tenancy; but in that case, an
| |
− |
| |
− | entity’s persisted data is stored in multiple, and multiple tenants can share all the tables.
| |
− |
| |
− | ===== @Multitenant Attributes =====
| |
− |
| |
− | {|
| |
− | |-
| |
− | ! Attribute
| |
− | ! Description
| |
− | ! Default
| |
− | ! Required?
| |
− | |-
| |
− | | <tt>MultitenantType</tt>
| |
− | | Specifies the multi-tenant strategy to use (<tt>SINGLE_TABLE</tt>).
| |
− | |
| |
− | | No
| |
− | |}
| |
− |
| |
− | ===== @TenantDiscriminatorColumn Attributes =====
| |
− |
| |
− | {|
| |
− | |-
| |
− | ! Attribute
| |
− | ! Description
| |
− | ! Default
| |
− | ! Required?
| |
− | |-
| |
− | | <tt>columnDefinition</tt>
| |
− | | The SQL fragment that is used when generating the DDL for the discriminator column.
| |
− | | The provider-generated SQL to create a column of the specified discriminator type.
| |
− | | No
| |
− | |-
| |
− | | <tt>contextProperty</tt>
| |
− | | The name of the context property to apply to the tenant discriminator column.
| |
− | | <tt>eclipselink.tenant-id</tt>
| |
− | | No
| |
− | |-
| |
− | | <tt>discriminatorType</tt>
| |
− | | The type of object/column to use as a class discriminator.
| |
− | | <tt>javax.persistence.DiscriminatorType.STRING</tt>
| |
− | | No
| |
− | |-
| |
− | | <tt>length</tt>
| |
− | | The column length for String-based discriminator types.
| |
− | | The column length for String-based discriminator types. Ignored for other discriminator types.
| |
− | | No
| |
− | |-
| |
− | | <tt>name</tt>
| |
− | | The name of column to be used for the tenant discriminator.
| |
− | | <tt>TENANT_ID</tt>
| |
− | | No
| |
− | |-
| |
− | | <tt>primaryKey</tt>
| |
− | | Specifies that the tenant discriminator column is part of the primary key of the tables.
| |
− | | Specifies that the tenant discriminator column is part of the primary key of the tables.
| |
− | | Yes
| |
− | |-
| |
− | | <tt>table</tt>
| |
− | | The name of the table that contains the column.
| |
− | | The name of the table that contains the column. If absent the column is assumed to be in the primary
| |
− | table. This attribute must be specified if the column is on a secondary table.
| |
− |
| |
− | | No
| |
− | |}
| |
− |
| |
− |
| |
− |
| |
− | == Configuring Single-Table Multi-Tenancy ==
| |
− |
| |
− | To configure single-table multi-tenancy, you must specify both of the following:
| |
− |
| |
− | #Annotate the entity or mapped superclass to use single-table multi-tenancy, using the
| |
− |
| |
− | <tt>@Multitenant</tt> annotation,
| |
− | <pre>@Multitenant</pre>
| |
− | annotation, for example:
| |
− |
| |
− | <source lang="java">
| |
− | @Entity
| |
− | @Table(name=“EMP”)
| |
− | @Multitenant(SINGLE_TABLE)
| |
− | </source>
| |
− |
| |
− | SINGLE_TABLE states that the table or tables (Table and SecondaryTable) associated with the given entity
| |
− |
| |
− | can be shared among tenants. 2. Specify the column or columns to be used as the discriminator column, using the
| |
− |
| |
− | @TenantDiscriminatorColumn annotation, for example: @Entity @Table(name=“EMP”) @Multitenant(SINGLE_TABLE) @TenantDiscriminatorColumn(name = “TENANT_ID”) You can specify multiple discriminator columns by using the @TableDiscriminatorColumns annotation, for
| |
− |
| |
− | example: @Entity @Table(name = "EMPLOYEE") @Multitenant(SINGLE_TABLE) @TenantDiscriminatorColumns({
| |
− |
| |
− | @TenantDiscriminatorColumn(name = "TENANT_ID")
| |
− | @TenantDiscriminatorColumn(name = "TENANT_CODE")})
| |
− |
| |
− |
| |
− |
| |
− | == Using Discriminator Columns ==
| |
− |
| |
− | The following characteristics apply to discriminator columns:
| |
− |
| |
− | * Tenant discriminator columns are completely application definable. REVIEWERS: I’m not sure what
| |
− |
| |
− | this sentence is after. Can I rewrite as “Discriminator columns must be defined by the application.”?
| |
− |
| |
− | * There is no limit on how many tenant discriminator columns an application can define.
| |
− | * Any name can be used for a discriminator column.
| |
− | * Tenant discriminator column(s) must always be used with @Multitenant(SINGLE_TABLE). You cannot
| |
− |
| |
− | specify the tenant discriminator column(s) only.
| |
− |
| |
− | * Generated schemas can include specified tenant discriminator columns.
| |
− | * Tenant discriminator columns can be mapped or unmapped:
| |
− |
| |
− | o When a tenant discriminator column is mapped, its associated mapping attribute must be marked as
| |
− |
| |
− | read only. With this restriction in place, a tenant discriminator column cannot be part of the entity
| |
− |
| |
− | identifier; it can only be part of the primary key specification on the database. o On persist, the value of a mapped tenant discriminator column mapping is populated from its
| |
− |
| |
− | associated context property. o Both mapped and unmapped properties are used to form the additional criteria when issuing a
| |
− |
| |
− | SELECT query. o When a tenant discriminator column is not mapped, the row is populated with the tenant
| |
− |
| |
− | discriminator column’s associated context property value. REVIEWERS: I have a question about this item from the design doc: “Unmapped tenant discriminator columns will require EclipseLink to populate the row with the tenant
| |
− |
| |
− | discriminator column’s associated context property value. See Core section below.” I am confused by this. There is also the item: “On persist, the value of a mapped tenant discriminator column mapping is populated from its associated
| |
− |
| |
− | context property. “ It sounds like these two might be saying the same thing for mapped and unmapped. What am I missing?
| |
− |
| |
− | == Using Single-Table Multi-Tenancy in an Inheritence Hierarchy ==
| |
− |
| |
− | Inheritance strategies are configured by specifying the inheritance type (see @inheritence in
| |
− |
| |
− | javax.persistence). Single-table multi-tenancy can be used in an inheritance hierarchy, as follows:
| |
− |
| |
− | *Multi-tenant metadata can only be applied at the root level of the inheritance hierarchy when using a
| |
− |
| |
− | SINGLE_TABLE or JOINED inheritance strategy.
| |
− |
| |
− | *It is possible to specify multi-tenant metadata within a TABLE_PER_CLASS inheritance hierarchy.
| |
− |
| |
− | REVIEWERS: How? Is it that the metadata can be applied somewhere down the hierarchy?
| |
− |
| |
− | == Annotation Examples ==
| |
− |
| |
− | The following example defines a single discriminator tenant column: @Entity @Table(name = "CUSTOMER") @Multitenant @TenantDiscriminatorColumn(name = "TENANT",
| |
− |
| |
− | contextProperty = "multi-tenant.id")
| |
− |
| |
− | public Customer() {
| |
− |
| |
− | ...
| |
− |
| |
− | } The following example defines 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() {
| |
− |
| |
− | ...
| |
− |
| |
− | } The following example defines a 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() {
| |
− |
| |
− | ...
| |
− |
| |
− | } The following example defines a 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; }
| |
− |
| |
− | == Specifying Metadata Using XML ==
| |
− |
| |
− | You can also use XML to configure single-table multi-tenancy, as an alternative to or in addition to
| |
− |
| |
− | using annotations. Use the following elements in the eclipselink-orm.xml file: <multitenant> <multitenant-type> <tenant-discriminator-column> For the EclipseLink schemas, see http://wiki.eclipse.org/EclipseLink/XSDs. REVIEWERS: The design doc makes it look like you don’t have to specify <multitenant-type> when using XML.
| |
− |
| |
− | Is that correct? XML Examples The following example defines a single tenant discriminator column: <entity class="model.Customer">
| |
− |
| |
− | <multitenant>
| |
− | <tenant-discriminator-column
| |
− | name="TENANT”
| |
− |
| |
− | context-property="multi-tenant.id"/>
| |
− |
| |
− | </multitenant>
| |
− | <table name="CUSTOMER"/>
| |
− | ...
| |
− |
| |
− | </entity> The following example defines 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> The following example defines a 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> The following example defines a 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>
| |
− |
| |
− | == Property Configuration and Caching Scope ==
| |
− |
| |
− | At runtime, context properties can be specified in a persistence unit definition or passed to a
| |
− |
| |
− | CreateEntityManagerFactory() call, as shown in the following examples.
| |
− |
| |
− | The order of precedence for tenant discriminator column properties is as follows, listed from highest
| |
− |
| |
− | priority to lowest: <-- REVIEWERS: is that right? Highest to lowest?
| |
− |
| |
− | #EntityManager
| |
− | #EntityManagerFactory
| |
− | #Application context (when in a Java EE container)
| |
− |
| |
− | The following example shows a property set in a persistence unit definition in the persistence.xml file: <persistence-unit name="multi-tenant">
| |
− |
| |
− | ...
| |
− | <properties>
| |
− | <property name="tenant.id" value="707"/>
| |
− | ...
| |
− | </properties>
| |
− |
| |
− | </persistence-unit> The following example shows the property set in code: HashMap properties = new HashMap(); properties.put("tenant.id", "707"); ... EntityManager em = Persistence.createEntityManagerFactory("multi-tenant",
| |
− |
| |
− | properties).createEntityManager();
| |
− |
| |
− | === Entity Manager Factory ===
| |
− |
| |
− | At the level of the entity manager factory, you must 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). For example: HashMap properties = new HashMap(); properties.put("tenant.id", "707"); properties.put("eclipselink.session-name", "multi-tenant-707"); ... EntityManager em = Persistence.createEntityManagerFactory("multi-tenant",
| |
− |
| |
− | properties).createEntityManager();
| |
− |
| |
− | === Shared Entity Manager Factory ===
| |
− |
| |
− | When using a shared entity manager factory, you must set following property to indicate the factory will
| |
− |
| |
− | be shared: eclipselink.multitenant.tenants-share-cache
| |
− |
| |
− | When this property is set, all multitenant entities will have a PROTECTED cache setting. Entity Manager
| |
− |
| |
− | At the level of the entity manager, you must specify caching strategies, because the same server session
| |
− |
| |
− | can be used for each tenant. For example, you can use an isolation level to ensure no shared tenant
| |
− |
| |
− | information exists in the L2 cache. These settings are set when creating the entity manager factory. For
| |
− |
| |
− | example: 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); ... Swapping tenant ID during a live EntityManager is not allowed.
| |
− |
| |
− | == Defining Persistence Unit and Entity Mappings Defaults ==
| |
− |
| |
− | You can define single-table multi-tenancy for specific entities and mapped superclasses, as described
| |
− |
| |
− | above. In addition, you can define the metadata at higher levels in the eclipselink-orm.xml file, to
| |
− |
| |
− | provide defaults. Standard JPA metadata defaulting and overriding rules apply. The elements used for
| |
− |
| |
− | specifying these defaults are:
| |
− |
| |
− | *<persistence-unit-defaults>
| |
− | *<entity-mappings>
| |
− |
| |
− | === Persistence Unit Defaults ===
| |
− |
| |
− | You can specify default tenant discriminator column metadata using the <persistence-unit-defaults>
| |
− |
| |
− | element and its <tenant-discriminator-column> subelement. When defined at this level, it applies to all
| |
− |
| |
− | entities of the persistence unit that specify the SINGLE_TABLE multi-tenant type, excluding those that
| |
− |
| |
− | specify their own tenant discriminator metadata. For example: REVIWERS: I came up with this example myself. Is it correct? If not, please advise.
| |
− |
| |
− | <persistence-unit-metadata>
| |
− |
| |
− | <persistence-unit-defaults>
| |
− | ...
| |
− | <tenant-discriminator-column
| |
− | name="TENANT”
| |
− |
| |
− | context-property="multi-tenant.id"/>
| |
− |
| |
− | </multitenant>
| |
− | </persistence-unit-defaults>
| |
− |
| |
− | </persistence-unit-metadata> Note: With no defaults, an entity not marked with multi-tenant metadata will not use any multi-tenancy
| |
− |
| |
− | strategy.
| |
− |
| |
− | === Entity Mappings Defaults ===
| |
− |
| |
− | You can also specify tenant discriminator column metadata using <entity-mappings> element. This overrides
| |
− |
| |
− | persistence unit defaults (described above) and applies to all entities of the given mapping file that
| |
− |
| |
− | specify the SINGLE_TABLE multi-tenant type, excluding those entities that specify their own tenant
| |
− |
| |
− | discriminator metadata. For example, REVIWERS: I came up with this example myself. Is it correct? If not, please advise.
| |
− |
| |
− | <entity-mappings >
| |
− |
| |
− | ...
| |
− | <tenant-discriminator-column
| |
− | name="TENANT”
| |
− |
| |
− | context-property="multi-tenant.id"/> <entity-mappings >
| |
− |
| |
− | === Example ===
| |
− |
| |
− | Defaults always apply even when there are multiple tenant discriminators. This allows you to map several
| |
− |
| |
− | columns for the same property. For example, the code below defaults the property name to
| |
− |
| |
− | "eclipselink.tenant-id" and states it should be writing the TENANT column for both the EMPLOYEE and
| |
− |
| |
− | SALARY table. @Entity @Table(name = "EMPLOYEE") @SecondaryTable(name = "SALARY") @MultiTenant(SINGLE_TABLE) @TenantDiscriminatorColumns({
| |
− |
| |
− | @TenantDiscriminatorColumn(name = "TENANT")
| |
− | @TenantDiscriminatorColumn(name = "TENANT", table = "SALARY")
| |
− | }
| |
− |
| |
− | ) public Employee() {
| |
− |
| |
− | ...
| |
− |
| |
− | }
| |
− |
| |
− | === Public Default Context Property ===
| |
− |
| |
− | The public default context property (eclipselink.tenant-id) definition is available from:
| |
− |
| |
− | *org.eclipse.persistence.config.EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT
| |
− | *org.eclipse.persistence.config.PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT
| |
− |
| |
− | For example:
| |
− |
| |
− | EntityManager em = createEntityManager(MULTI_TENANT_PU); em.setProperty("tenant.id", "707"); em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "707");
| |
− |
| |
− | Support for Tenant Discriminator Columns through Entity Manager Operations and Querying The tenant discriminator column and value are supported through the following entity manager operations:
| |
− |
| |
− | *persist()
| |
− | *find()
| |
− | *refresh()
| |
− |
| |
− | They are also supported through the following queries:
| |
− |
| |
− | *Named queries
| |
− | *Update all
| |
− | *Delete all
| |
− |
| |
− | REVIEWERS: Do you think any examples would be appropriate here? If so, please provide. Note: EclipseLink does not support multi-tenancy through named native queries. If you want to use named
| |
− |
| |
− | native queries in a multi-tenant environment, you must handle any multi-tenancy issues directly in the
| |
− |
| |
− | query. In general, it is best to avoid named native queries in a multi-tenant environment.
| |