6 Providing Software as a Service

This chapter provides instructions for creating shared TopLink applications that run in software as a service (SaaS) environments.

This chapter includes the following sections:

6.1 Understanding Oracle TopLink as a SaaS

The Oracle Platform for SaaS includes TopLink, part of Oracle Fusion Middleware. This allows you to build, deploy, and manage SaaS applications. With TopLink you can manage persistence in a cloud-enabled applications and services. Developing more-flexible SaaS solutions that address multi-tenancy and extensibility while still maintaining high performance and scalability makes the persistence layer of these applications a critical component.

TopLink supports providing software as a service by supporting extensibility, multi-tenancy, and the ability to use external metadata sources, as explained in the following sections:

6.2 Making JPA Entities Extensible

Use the @VirtualAccessMethods annotation to specify that an entity is extensible. By using virtual properties in an extensible entity, you can specify mappings external to the entity. This allows you to modify the mappings without modifying the entity source file and without redeploying the entity's persistence unit.

Extensible entities are useful in a multi-tenant (or SaaS) environment where a shared, generic application can be used by multiple clients (tenants). Tenants have private access to their own data, and to data shared with other tenants.

Using extensible entities, you can:

  • Build an application where some mappings are common to all users and some mappings are user-specific.

  • Add mappings to an application after it is made available to a customer (even post-deployment).

  • Use the same EntityManagerFactory to work with data after mappings have changed.

  • Provide an additional source of metadata to be used by an application.

6.2.1 Main Tasks

To create and support an extensible JPA entity:

6.2.1.1 Task 1: Configure the Entity

Configuring the entity consists of annotating the entity class with @VirtualAccessMethods, adding get and set methods for the property values, and adding a data structure to store the extended attributes and values.

6.2.1.1.1 Annotate the Entity Class with @VirtualAccessMethods

Annotate the entity with @VirtualAccessMethods to specify that it is extensible and to define virtual properties.

Table 6-1 describes the attributes available to the @VirtualAccessMethods annotation.

Table 6-1 Attributes for the @VirtualAccessMethods Annotation

Attribute Description

get

The name of the getter method to use for the virtual property. This method must take a single java.lang.String parameter and return a java.lang.Object.

Default: get

Required? No

set

The name of the setter method to use for the virtual property. This method must take a java.lang.String and a java.lang.Object parameter and return a java.lang.Object parameter.

Default: set

Required? No


6.2.1.1.2 Add get and set Methods to the Entity

Add get(String) and set(String, Object) methods to the entity. The get() method returns a value by property name and the set() method stores a value by property name. The default names for these methods are get and set, and they can be overridden with the @VirtualAccessMethods annotation.

EclipseLink weaves these methods if weaving is enabled, which provides support for lazy loading, change tracking, fetch groups, and internal optimizations. You must use the get(String) and set(String, Object) signatures, or else weaving will not work.

Note:

Weaving is not supported when using virtual access methods with OneToOne mappings. If attempted, an exception will be thrown.

6.2.1.1.3 Add a Data Structure

Add a data structure to store the extended attributes and values, that is, the virtual mappings. These can then be mapped to the database. See Section 6.2.1.3, "Task 3: Provide Additional Mappings."

A common way to store the virtual mappings is in a Map (as shown in Example 6-1), but you can also use other strategies. For example you could store the virtual mappings in a directory system.

When using field-based access, annotate the data structure with @Transient so the structure cannot be used for another mapping. When using property-based access, @Transient is unnecessary.

Example 6-1 illustrates an entity class that uses property access.

Example 6-1 Entity Class that Uses Property Access

@Entity
@VirtualAccessMethods
public class Customer{
 
    @Id
    private int id;
    ...
 
    @Transient
    private Map<String, Object> extensions;
 
    public <T> T get(String name) {
        return (T) extentions.get(name);
    }
 
    public Object set(String name, Object value) {
        return extensions.put(name, value);
    }
6.2.1.1.4 Use XML

As an alternative to, or in addition to, using @VirtualAccessMethods, you can use the <access> and <access-methods> elements, for example:

<access>VIRTUAL</access>
<access-methods set-method="get" get-method="set"/>

6.2.1.2 Task 2: Design the Schema

Provide database tables with extra columns for storing flexible mapping data. For example, the following Customer table includes two predefined columns, ID and NAME, and three flexible columns, FLEX_COL1, FLEX_COL2, FLEX_COL3:

  • CUSTOMER

    • INTEGER ID

    • VARCHAR NAME

    • VARCHAR FLEX_COL1

    • VARCHAR FLEX_COL2

    • VARCHAR FLEX_COL3

You can then specify which of the flex columns should be used to persist an extended attribute, as described in "Task 3: Provide Additional Mappings".

6.2.1.3 Task 3: Provide Additional Mappings

To provide additional mappings, add the mappings with the column and access-methods attributes to the eclipselink-orm.xml file, for example:

<basic name="idNumber" attribute-type="String">
  <column name="FLEX_COL1"/>
  <access-methods get-method="get" set-method="set"/>
</basic>

6.2.1.4 Task 4: Configure Persistence Properties and the Data Repository

Configure persistence unit properties to indicate that the application should retrieve the flexible mappings from the eclipselink-orm.xml file. You can set persistence unit properties using persistence.xml or by setting properties on the EntityManagerFactory, as described in the following sections.

For more information about external mappings, see "External Mappings" in the EclipseLink documentation.

http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Advanced_JPA_Development/External_Mappings

6.2.1.4.1 Configure persistence.xml

In persistence.xml file, use the eclipselink.metadata-source property to use the default eclipselink-orm.xml file. Use the eclipselink.metadata-source.xml.url property to use a different file at the specified location, for example:

<property name="eclipselink.metadata-source" value="XML"/>
<property name="eclipselink.metadata-source.xml.url" value="foo://bar"/>
6.2.1.4.2 Configure the EntityManagerFactory and the Metadata Repository

Extensions are added at bootstrap time through access to a metadata repository. The metadata repository is accessed through a class that provides methods to retrieve the metadata it holds. The current release includes a metadata repository implementation that supports XML repositories.

Specify the class to use and any configuration information for the metadata repository through persistence unit properties. The entity manager factory integrates additional mapping information from the metadata repository into the metadata it uses to bootstrap.

You can provide your own implementation of the class to access the metadata repository. Each metadata repository access class must specify an individual set of properties to use to connect to the repository.

You can subclass either of the following classes:

  • org.eclipse.persistence.internal.jpa.extensions.MetadataRepository

  • org.eclipse.persistence.internal.jpa.extensions.XMLMetadataRepository

In the following example, the properties that begin with com.foo are defined by the developer.

<property name="eclipselink.metadata-source" value="com.foo.MetadataRepository"/>
<property name="com.foo.MetadataRepository.location" value="foo://bar"/>
<property name="com.foo.MetadataRepository.extra-data" value="foo-bar"/>
6.2.1.4.3 Refresh the Metadata Repository

If you change the metadata and you want an EntityManager based on the new metadata, you must call refreshMetadata() on the EntityManagerFactory to refresh the data. The next EntityManager will be based on the new metadata.

The refreshMetadata method takes a Map of properties, and that map of properties can be used to override the properties previously defined for the metadata-source.

6.2.2 Code Examples

Example 6-2 illustrates the following:

  • Field access is used for non-extension fields.

  • Virtual access is used for extension fields, using defaults (get(String) and set(String, Object)).

  • The get(String) and set(String, Object) methods will be woven, even if no mappings use them, because of the presence of @VirtualAccessMethods.

  • Extensions are mapped in a portable way by specifying @Transient.

Example 6-2 Virtual Access Using Default get and set Method Names

@Entity
@VirtualAccessMethods
public class Address {
 
    @Id
    private int id;
 
    @Transient
    private Map<String, Object> extensions;
 
    public int getId(){
        return id;
    }
 
    public <T> T get(String name) {
        return (T) extentions.get(name);
    }
 
    public Object set(String name, Object value) {
        return extensions.put(name, value);
    }
 
   ...
 

Example 6-3 illustrates the following:

  • Field access is used for non-extension fields.

  • The @VirtualAccessMethods annotation overrides methods to be used for getting and setting.

  • The get(String) and set(String, Object) methods will be woven, even if no mappings use them, because of the presence of @VirtualAccessMethods.

  • Extensions are mapped in a portable way by specifying @Transient.

  • The XML for extended mapping indicates which get() and set() method to use.

Example 6-3 Overriding Get and Set Methods

@Entity
@VirtualAccessMethods(get="getExtension", set="setExtension")
public class Address {
 
    @Id
    private int id;
 
    @Transient
    private Map<String, Object> extensions;
 
    public int getId(){
        return id;
    }
 
    public <T> T getExtension(String name) {
        return (T) extensions.get(name);
    }
 
    public Object setExtension(String name, Object value) {
        return extensions.put(name, value);
    }
 
    ...
 
    <basic name="name" attribute-type="String">
      <column name="FLEX_1"/>
      <access-methods get-method="getExtension" set-method="setExtension"/>
    </basic>
 

Example 6-4 illustrates the following:

  • Property access is used for non-extension fields.

  • Virtual access is used for extension fields, using defaults (get(String) and set(String, Object)).

  • The extensions are mapped in a portable way. @Transient is not required, because property access is used.

  • The get(String) and set(String, Object) methods will be woven, even if no mappings use them, because of the presence of @VirtualAccessMethods.

Example 6-4 Using Property Access

@Entity
@VirtualAccessMethods
public class Address {
 
    private int id;
 
    private Map<String, Object> extensions;
 
    @Id
    public int getId(){
        return id;
    }
 
    public <T> T get(String name) {
        return (T) extensions.get(name);
    }
 
    public Object set(String name, Object value) {
        return extensions.put(name, value);
    }
 
...

6.3 Making JAXB Beans Extensible

Use the @XmlVirtualAccessMethods annotation to specify that a JAXB bean is extensible. By using virtual properties in an extensible bean, you can specify mappings external to the bean. This allows you to modify the mappings without modifying the bean source file and without redeploying the bean's persistence unit.

In a multi-tenant (or SaaS) architecture, a single application runs on a server, serving multiple client organizations (tenants). Good multi-tenant applications allow per-tenant customizations. When these customizations are made to data, it can be difficult for the binding layer to handle them. JAXB is designed to work with domain models that have real fields and properties. EclipseLink Object-XML 2.3 (also known as MOXy) introduces the concept of virtual properties which can easily handle this use case. Virtual properties are defined by the Object-XML metadata file, and provide a way to extend a class without modifying the source.

This section has the following subsections:

6.3.1 Main Steps

To create and support an extensible JAXB bean:

6.3.1.1 Task 1: Configure the Bean

Configuring the bean consists of annotating the bean class with the @XmlVirtualAccessMethods, adding get and set methods for the property values, and adding a data structure to store the extended attributes and values.

6.3.1.1.1 Annotate the Bean Class with @Xml VirtualAccessMethods

Annotate the bean with @XmlVirtualAccessMethods to specify that it is extensible and to define virtual properties.

Table 6-2 describes the attributes available to the @XmlVirtualAccessMethods annotation.

Table 6-2 Attributes for the @XmlVirtualAccessMethods Annotation

Attribute Description

get

The name of the getter method to use for the virtual property. This method must take a single java.lang.String parameter and return a java.lang.Object.

Default: get

Required? No

set

The name of the setter method to use for the virtual property. This method must take a java.lang.String and a java.lang.Object parameter and return a java.lang.Object parameter.

Default: set

Required? No


6.3.1.1.2 Add get and set Methods to the Bean

Add get(String) and set(String, Object) methods to the bean. The get() method returns a value by property name and the set() method stores a value by property name. The default names for these methods are get and set, and they can be overridden with the @XmlVirtualAccessMethods annotation.

EclipseLink weaves these methods if weaving is enabled, which provides support for lazy loading, change tracking, fetch groups, and internal optimizations.

6.3.1.1.3 Add a Data Structure

Add a data structure to store the extended attributes and values, that is, the virtual mappings. These can then be mapped to the database. See "Task 2: Provide Additional Mappings".

A common way to store the virtual mappings is in a Map, but you can use other ways, as well. For example you could store the virtual mappings in a directory system.

When using field-based access, annotate the data structure with @XmlTransient so it cannot use it for another mapping. When using property-based access, @XmlTransient is unnecessary.

6.3.1.1.4 Use XML

As an alternative to, or in addition to, using @XmlVirtualAccessMethods, you can use the <access> and <access-methods> elements, for example:

<access>VIRTUAL</access>
<access-methods set-method="get" get-method="set"/>

XML to enable virtual access methods using get and set:

<xml-virtual-access-methods/>

XML to enable virtual access methods using put instead of set (default):

<xml-virtual-access-methods set-method="put"/>

XML to enable virtual access methods using retrieve instead of get (default):

<xml-virtual-access-methods get-method="retrieve"/>

XML to enable virtual access methods using retrieve and put instead of get and set (default):

<xml-virtual-access-methods get-method="retrieve" set-method="put"/>

6.3.1.2 Task 2: Provide Additional Mappings

To provide additional mappings, add the mappings to the eclipselink-oxm.xml file, for example:

<xml-element java-attribute="idNumber"/> 

6.3.2 Code Examples

The examples in this section illustrate how to use extensible JAXB beans. The example begins with the creation of a base class that other classes can extend. In this case the extensible classes are for Customers and PhoneNumbers. Mapping files are created for two separate tenants. Even though both tenants share several real properties, they will define virtual properties that are unique to their requirements.

6.3.2.1 Basic Setup

Example 6-5 illustrates a base class, ExtensibleBase, which other extensible classes can extend. In the example, the use of the @XmlTransient annotation prevents ExtensibleBase from being mapped as an inheritance relationship. The real properties represent the parts of the model that will be common to all tenants. The per-tenant extensions will be represented as virtual properties.

Example 6-5 A Base Class for Extensible Classes

package examples.virtual;
 
import java.util.HashMap;
import java.util.Map;
 
import javax.xml.bind.annotation.XmlTransient;
 
import org.eclipse.persistence.oxm.annotations.XmlVirtualAccessMethods;
 
@XmlTransient
@XmlVirtualAccessMethods(setMethod="put")
public class ExtensibleBase {
 
    private Map<String, Object> extensions = new HashMap<String, Object>();
 
    public <T> T get(String property) {
        return (T) extensions.get(property);
    }
 
    public void put(String property, Object value) {
        extensions.put(property, value);
    }
}

Example 6-6 illustrates the definition of a Customer class. The Customer class is extensible because it inherits from a domain class that has been annotated with @XmlVirtualAccessMethods.

Example 6-6 An Extensible Customer Class

package examples.virtual;
 
import javax.xml.bind.annotation.XmlRootElement;
 
@XmlRootElement
public class Customer extends ExtensibleBase {
 
    private String firstName;
    private String lastName;
    private Address billingAddress;
 
    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
 
    public String getLastName() {
        return lastName;
    }
 
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
 
    public Address getBillingAddress() {
        return billingAddress;
    }
 
    public void setBillingAddress(Address billingAddress) {
        this.billingAddress = billingAddress;
    }
 
}

Example 6-7 illustrates an Address class. It is not necessary for every class in your model to be extensible. In this example, the Address class does not have any virtual properties.

Example 6-7 A Nonextensible Address Class

package examples.virtual;
 
public class Address {
 
    private String street;
 
    public String getStreet() {
        return street;
    }
 
    public void setStreet(String street) {
        this.street = street;
    }
 
}

Example 6-8 illustrates a PhoneNumber class. Like Customer, PhoneNumber will be an extensible class.

Example 6-8 An Extensible PhoneNumber Class

package examples.virtual;
 
import javax.xml.bind.annotation.XmlValue;
 
public class PhoneNumber extends ExtensibleBase {
 
    private String number;
 
    @XmlValue
    public String getNumber() {
        return number;
    }
 
    public void setNumber(String number) {
        this.number = number;
    }
 
}

6.3.2.2 Define the Tenants

The examples in this section define two separate tenants. Even though both tenants share several real properties, the corresponding XML representation can be quite different due to virtual properties.

Tenant 1

The first tenant is an online sporting goods store that requires the following extensions to its model:

  • Customer ID

  • Customer's middle name

  • Shipping address

  • A collection of contact phone numbers

  • Type of phone number (that is, home, work, or cell)

The metadata for the virtual properties is supplied through Object-XML's XML mapping file. Virtual properties are mapped in the same way as real properties. Some additional information is required, including type (since this cannot be determined through reflection), and for collection properties, a container type. The virtual properties defined below for Customer are middleName, shippingAddress, and phoneNumbers. For PhoneNumber, the virtual property is the type property.

Example 6-9 illustrates the binding-tenant1.xml mapping file.

Example 6-9 Defining Virtual Properties for Tenant 1

<?xml version="1.0"?>
<xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="examples.virtual">
    <java-types>
        <java-type name="Customer">
            <xml-type prop-order="firstName middleName lastName billingAddress shippingAddress phoneNumbers"/>
            <java-attributes>
                <xml-attribute
                    java-attribute="id"
                    type="java.lang.Integer"/>
                <xml-element
                    java-attribute="middleName"
                    type="java.lang.String"/>
                <xml-element
                    java-attribute="shippingAddress"
                    type="examples.virtual.Address"/>
                <xml-element
                    java-attribute="phoneNumbers"
                    name="phoneNumber"
                    type="examples.virtual.PhoneNumber"
                    container-type="java.util.List"/>
            </java-attributes>
        </java-type>
        <java-type name="PhoneNumber">
            <java-attributes>
                <xml-attribute
                    java-attribute="type"
                    type="java.lang.String"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

The get and set methods are used on the domain model to interact with the real properties and the accessors defined on the @XmlVirtualAccessMethods annotation are used to interact with the virtual properties. The normal JAXB mechanisms are used for marshal and unmarshal operations. Example 6-10 illustrates the Customer class code for tenant 1 to obtain the data associated with virtual properties.

Example 6-10 Tenant 1 Code to Provide the Data Associated with Virtual Properties

...
Customer customer = new Customer();
 
//Set Customer's real properties
customer.setFirstName("Jane");
customer.setLastName("Doe");
 
Address billingAddress = new Address();
billingAddress.setStreet("1 Billing Street");
customer.setBillingAddress(billingAddress);
 
//Set Customer's virtual 'middleName' property
customer.put("middleName", "Anne");
 
//Set Customer's virtual 'shippingAddress' property
Address shippingAddress = new Address();
shippingAddress.setStreet("2 Shipping Road");
customer.put("shippingAddress", shippingAddress);
 
List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>();
customer.put("phoneNumbers", phoneNumbers);
 
PhoneNumber workPhoneNumber = new PhoneNumber();
workPhoneNumber.setNumber("555-WORK");
//Set the PhoneNumber's virtual 'type' property
workPhoneNumber.put("type", "WORK");
phoneNumbers.add(workPhoneNumber);
 
PhoneNumber homePhoneNumber = new PhoneNumber();
homePhoneNumber.setNumber("555-HOME");
//Set the PhoneNumber's virtual 'type' property
homePhoneNumber.put("type", "HOME");
phoneNumbers.add(homePhoneNumber);
 
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "examples/virtual/binding-tenant1.xml");
JAXBContext jc = JAXBContext.newInstance(new Class[] {Customer.class, Address.class}, properties);
 
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(customer, System.out);
...
 

Example 6-11 illustrates the XML output from the Customer class for tenant 1.

Example 6-11 XML Output from the Customer Class for Tenant 1

<?xml version="1.0" encoding="UTF-8"?>
<customer>
   <firstName>Jane</firstName>
   <middleName>Anne</middleName>
   <lastName>Doe</lastName>
   <billingAddress>
      <street>1 Billing Street</street>
   </billingAddress>
   <shippingAddress>
      <street>2 Shipping Road</street>
   </shippingAddress>
   <phoneNumber type="WORK">555-WORK</phoneNumber>
   <phoneNumber type="HOME">555-HOME</phoneNumber>
</customer>

Tenant 2

The second tenant is a streaming media provider that offers on-demand movies and music to its subscribers. It requires a different set of extensions to the core model:

  • A single contact phone number

For this tenant, the mapping file is also used to customize the mapping of the real properties.

Example 6-12 illustrates the binding-tenant2.xml mapping file.

Example 6-12 Defining Virtual Properties for Tenant 2

<?xml version="1.0"?>
<xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="examples.virtual">
    <xml-schema namespace="urn:tenant1" element-form-default="QUALIFIED"/>
    <java-types>
        <java-type name="Customer">
            <xml-type prop-order="firstName lastName billingAddress phoneNumber"/>
            <java-attributes>
                <xml-attribute java-attribute="firstName"/>
                <xml-attribute java-attribute="lastName"/>
                <xml-element java-attribute="billingAddress" name="address"/>
                <xml-element
                    java-attribute="phoneNumber"
                    type="examples.virtual.PhoneNumber"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

Example 6-13 illustrates the tenant 2 Customer class code to obtain the data associated with virtual properties.

Example 6-13 Tenant 2 Code to Provide the Data Associated with Virtual Properties

...
Customer customer = new Customer();
customer.setFirstName("Jane");
customer.setLastName("Doe");
 
Address billingAddress = new Address();
billingAddress.setStreet("1 Billing Street");
customer.setBillingAddress(billingAddress);
 
PhoneNumber phoneNumber = new PhoneNumber();
phoneNumber.setNumber("555-WORK");
customer.put("phoneNumber", phoneNumber);
 
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "examples/virtual/binding-tenant2.xml");
JAXBContext jc = JAXBContext.newInstance(new Class[] {Customer.class, Address.class}, properties);
 
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(customer, System.out);
...

Example 6-14 illustrates the XML output from the Customer class for tenant 2.

Example 6-14 XML Output from the Customer Class for Tenant 2

<?xml version="1.0" encoding="UTF-8"?>
<customer xmlns="urn:tenant1" firstName="Jane" lastName="Doe">
   <address>
      <street>1 Billing Street</street>
   </address>
   <phoneNumber>555-WORK</phoneNumber>
</customer>

6.4 Using Single-Table Multi-Tenancy

A key element to implementing SaaS is the ability for multiple application tenants to use a shared persistence schema while ensuring that the tenant only works on its own data. Single-table multi-tenancy uses a single-table for all application tenants and differentiates application tenants based on tenant discriminator columns with specific application context values. Applications can configure as many discriminator columns as needed or rely on default behavior. For additional details on single-table multi-tenancy, see:

http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Advanced_JPA_Development/Single-Table_Multi-Tenancy

The following topics are included in this section:

6.4.1 Main Tasks

The tasks in this section provide instructions for using single-table multi-tenancy when creating applications that are designed to run in SaaS environments.

The following tasks are included in this section:

6.4.1.1 Task 1: Enable Single-Table Multi-Tenancy

Single-table multi-tenancy can be enabled declaratively using the @Multitenant annotation; or in an ORM XML file using the <multitenant> element; or using annotations and XML together.

Using the @Multitenant Annotation

To use the @Multitenant annotation, include the annotation with an @Entity or @MappedSuperclass annotation and include the SINGLE_TABLE attribute. For example:

@Entity
@Multitenant(SINGLE_TABLE)
public class Employee {
}

The SINGLE_TABLE attributes states that the table or tables (Table and SecondaryTable) associated with the given entity can be shared among tenants.

Using the <multitenant> Element

To use the <multitenant> element, include the element within an <entity> element. For example:

<entity class="model.Employee">
   <multitenant type="SINGLE_TABLE">
   ...
   </multitenant>
   ...
</entity>

6.4.1.2 Task 2: Specify Tenant Discriminator Columns

Discriminator columns are used together with an associated application context to indicate which rows in a table an application tenant can access. Multiple tenant discriminator columns can be specified. If no tenant discriminator column is specified a default column named TENANT_ID is used along with the default eclipselink.tenant-id context property. The tenant discriminator column is assumed to be on the primary table unless a table or secondary table is explicitly specified. To change the default behavior, see:

http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Advanced_JPA_Development/Single-Table_Multi-Tenancy#Defining_Persistence_Unit_and_Entity_Mappings_Defaults

Tenant discriminator columns can be specified declaratively using the @TenantDiscriminatorColumn or @TenantDiscriminatorColumns annotations; or in an ORM XML file using the <tenant-discriminator-column> element.

Using the @TenantDiscriminatorColumn Annotation

To use the @TenantDiscriminatorColumn annotation, include the annotation with an @Entity or @MappedSuperclass annotation and include the name and contextProperty attributes. For example:

@Entity
@Multitenant(SINGLE_TABLE)
@TenantDiscriminatorColumn(name = "TENANT", contextProperty = "multi-tenant.id")
public class Employee {
}

To specify multiple columns, include multiple @TenantDiscriminatorColumn annotations within the @TenantDiscriminatorColumns annotation and include the table where the column is located if it is not located on the primary table. For example:

@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() {
   ...
}

Using the <tenant-discriminator-column> Element

To use the <tenant-discriminator-column> element, include the element within a <multitenant> element and include the name and context-property attributes. For example:

<entity class="model.Employee">
   <multitenant>
      <tenant-discriminator-column name="TENANT"
         context-property="multi-tenant.id"/>
   </multitenant>
   ...
</entity>

To specify multiple columns, include additional <tenant-discriminator-column> elements and include the table where the column is located if it is not located on the primary table. For example:

<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>

Mapping Tenant Discriminator Columns

Tenant discriminator columns can be mapped to a primary key or another column. The following example maps the tenant discriminator column to the primary key on the table during DDL generation:

@Entity
@Table(name = "ADDRESS")
@Multitenant
@TenantDiscriminatorColumn(name = "TENANT", contextProperty = "tenant.id",
   primaryKey = true)
public Address() {
  ...
}

To have the discriminator column mapped to the primary key as part of the object entity, the column must be mapped. For example

@Id
@Column("TENANT")
public int tenant;

The following example maps the tenant discriminator column to a primary key in the ORM XML file:

<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 maps the tenant discriminator column to another column name AGE.

@Entity
@Table(name = "Player")
@Multitenant
@TenantDiscriminatorColumn(name = "AGE", contextProperty = "tenant.age")
public Player() {
  ...
 
  @Basic
  @Column(name="AGE", insertable="false", updatable="false")
  public int age;
}

Or, in the ORM XML file as follows:

<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>

Specifying a context property at Run Time

At runtime, the context property configuration can be specified using a persistence unit definition that is passed to a create entity manager factory call or set on an individual entity manager. For example

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

6.4.1.3 Task 3: Use the Discriminator Column at Run Time

The tenant discriminator column can be used at run time through entity manager operations and querying. The tenant discriminator column and value are supported through the following entity manager operations:

  • persist()

  • find()

  • refresh()

The tenant discriminator column and value are supported through the following queries:

  • Named queries

  • Update all

  • Delete all

Note:

Multi-tenancy is not supported through named native queries. To use named native queries in a multi-tenant environment, manually handle any multi-tenancy issues directly in the query. In general, it is best to avoid named native queries in a multi-tenant environment.

6.4.2 Additional Resources

The following additional resources are available:

6.4.2.2 Related Javadoc

For more information, see the following APIs in Oracle Fusion Middleware Java API Reference for Oracle TopLink.

  • org.eclipse.persistence.annotations.Multitenant

  • org.eclipse.persistence.annotations.TenantDiscriminatorColumn

  • org.eclipse.persistence.annotations.TenantDiscriminatorColumns

6.5 Using an External Metadata Source

With TopLink, you can store your mapping information in a metadata source that is external to the running application. Because the mapping information is retrieved when the application creates the persistence unit, you can dynamically override or extend mappings in a deployed application.

6.5.1 Using the eclipselink-orm.xml File Externally

With TopLink, you can use the eclipselink-orm.xml file to support advanced mapping types and options. This file can override the standard JPA orm.xml mapping configuration file.

6.5.2 Main Tasks

This section includes the following tasks:

6.5.2.1 Task 1: Configure the Persistence Unit

You can configure your persistence unit to use the external metadata by:

6.5.2.1.1 Accessing a Fixed Location

The easiest way to access an external file, such as the eclipselink-orm.xml file with additional mapping information, is by making the file available from a fixed URL on the web server.

Use the eclipselink.metadata-source.xml.url property, as shown in Example 6-15, to specify the location:

Example 6-15 Fixed Location

<property name="eclipselink.metadata-source" value="XML"/>
<property name="eclipselink.metadata-source.xml.url" value="http://myserverlocation/"/>
6.5.2.1.2 Accessing an Application Context Based Location

For more complex requirements, such as providing tenant-specific extensions in a multi-tenant application, you can specify the location of the external metadata based on the application context.

Implement the MetadataSource interface, as shown in Example 6-16, to specify the location:

Example 6-16 Fixed Location

<property name="eclipselink.metadata-source" value="mypackage.MyMetadataSource"/>
<property name="eclipselink.metadata-source.xml.url" value="foo://bar"/>

Example 6-17 illustrates how to return a specific mapping file, based on tenant:

Example 6-17 Tenant-specific Mapping File

public class AdminMetadataSource extends XMLMetadataSource {
 
    @Override
    public XMLEntityMappings getEntityMappings(Map<String, Object> properties, ClassLoader classLoader, SessionLog log) {
        String leagueId = (String) properties.get(LEAGUE_CONTEXT);
        properties.put(PersistenceUnitProperties.METADATA_SOURCE_XML_URL, "http://myserverlocation/rest/" + leagueId + "/orm");
        return super.getEntityMappings(properties, classLoader, log);
    }
}

6.5.2.2 Task 2: Configure the Server

To access the metadata file, the server must provide URL access to the mapping file by using:

  • Static file serving,

  • A server-based solution with its own mapping file or a mapping file built on-demand from stored mapping information,

  • Or some other web technology.

6.5.3 Additional Resources

For additional information on JPA deployment, see the following sections of the JPA Specification (http://jcp.org/en/jsr/detail?id=317):

  • Section 7.2, "Bootstrapping in Java SE Environments"

  • Chapter 7, "Container and Provider Contracts for Deployment and Bootstrapping"

6.5.3.1 Javadoc

For more information, see the following APIs in Oracle Fusion Middleware Java API Reference for Oracle TopLink.

  • PersistenceUnitProperties class