ヘッダーをスキップ
Oracle® Fusion Middleware Oracle TopLinkソリューション・ガイド
11g リリース1 (11.1.1)
B66616-01
  目次へ移動
目次

前
 
次
 

6 サービスとしてのソフトウェアの提供

この章では、Software as a Service (SaaS)環境で動作する共有TopLinkアプリケーションを作成する手順を説明します。

この章では、次の項目について説明します。

6.1 SaaSとしてのOracle TopLinkの理解

SaaS用のOracleプラットフォームには、Oracle Fusion Middlewareの一部であるTopLinkが含まれます。これにより、SaaSアプリケーションのビルド、デプロイおよび管理が可能になっています。TopLinkでは、クラウド対応のアプリケーションおよびサービスで永続性を管理できます。高いパフォーマンスとスケーラビリティを維持しながら、マルチテナントおよび拡張性に対処する、より柔軟なSaaSソリューションを開発するには、これらのアプリケーションの永続性レイヤーが重要なコンポーネントになります。

TopLinkでは、拡張性、マルチテナント、および外部メタデータ・ソースの使用をサポートすることにより、サービスとしてのソフトウェアの提供をサポートしており、次の項に、その詳細が説明されています。

6.2 JPAエンティティの拡張可能化

@VirtualAccessMethods注釈を使用して、エンティティが拡張可能であることを指定します。拡張可能なエンティティで仮想プロパティを使用すれば、エンティティの外部でマッピングを指定できます。これにより、エンティティのソース・ファイルの変更およびエンティティの永続性ユニットの再デプロイをしなくても、マッピングを変更できるようになります。

拡張可能なエンティティは、複数のクライアント(テナント)が共有の汎用アプリケーションを使用できるマルチテナント(またはSaaS)環境で使用するのに役立ちます。テナントは、自分のデータにプライベートにアクセスでき、他のテナントと共有するデータにもアクセスできます。

拡張可能なエンティティを使用すれば、次のことを行えます。

6.2.1 主なタスク

拡張可能なJPAエンティティを作成およびサポートするには:

6.2.1.1 タスク1: エンティティの構成

エンティティの構成では、@VirtualAccessMethodsによるエンティティ・クラスの注釈指定、プロパティ値に対するgetおよびsetメソッドの追加、および拡張された属性と値をストアするデータ構造の追加をします。

6.2.1.1.1 @VirtualAccessMethodsによるエンティティ・クラスの注釈指定

@VirtualAccessMethodsでエンティティに注釈を付けて、エンティティが拡張可能であることを指定し、仮想プロパティを定義します。

表6-1は、@VirtualAccessMethods注釈で使用可能な属性を説明しています。

表6-1 @VirtualAccessMethods注釈の属性

属性 説明

get

仮想プロパティに使用するgetterメソッドの名前。このメソッドは、1つのjava.lang.Stringパラメータを受け取ってjava.lang.Objectを返す必要があります。

デフォルト: get

必須かどうか:いいえ

set

仮想プロパティに使用するsetterメソッドの名前。このメソッドは、java.lang.Stringおよびjava.lang.Objectパラメータを受け取ってjava.lang.Objectパラメータを返す必要があります。

デフォルト: set

必須かどうか:いいえ


6.2.1.1.2 エンティティに対するgetおよびsetメソッドの追加

エンティティに対して、get(String)およびset(String, Object)メソッドを追加します。get()メソッドは、プロパティ名によって値を返し、set()メソッドはプロパティ名によって値をストアします。これらのメソッドのデフォルト名は、getおよびsetで、@VirtualAccessMethods注釈でオーバーライドできます。

EclipseLinkでは、ウィービングが有効な場合に、これらのメソッドがウィービングされて、遅延ロード、変更追跡、フェッチ・グループおよび内部最適化がサポートされます。get(String)およびset(String, Object)シグネチャを使用する必要があります。そうしないと、ウィービングは動作しません。


注意:

OneToOneマッピングで仮想アクセス・メソッドを使用する場合、ウィービングはサポートされません。使用すると、例外がスローされます。


6.2.1.1.3 データ構造の追加

拡張された属性と値、つまり仮想マッピングをストアするデータ構造を追加します。その後、これらをデータベースにマップできます。6.2.1.3項「タスク3: 追加マッピングの提供」を参照してください。

仮想マッピングは(例6-1に示されているような)Mapにストアするのが一般的ですが、他の方式を使用することもできます。たとえば、ディレクトリ・システムに仮想マッピングをストアすることもできます。

フィールドベースのアクセスを使用する際には、@Transientでデータ構造に注釈を付けて、この構造を別のマッピングに使用できないようにします。プロパティベースのアクセスを使用する場合、@Transientは不要です。

例6-1に、プロパティ・アクセスを使用するエンティティ・クラスを示します。

例6-1 プロパティ・アクセスを使用するエンティティ・クラス

@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 XMLの使用

@VirtualAccessMethodsを使用する方法に加えて、代替方法として、<access>および<access-methods>要素を、次のように使用することもできます。

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

6.2.1.2 タスク2: スキーマの設計

フレキシブルなマッピング・データを保存するために、余分な列があるデータベース表を用意します。たとえば、次のCustomer表には、事前定義された2つの表、IDおびNAMEがあり、3つのフレキシブル列、FLEX_COL1FLEX_COL2FLEX_COL3があります。

  • CUSTOMER

    • INTEGER ID

    • VARCHAR NAME

    • VARCHAR FLEX_COL1

    • VARCHAR FLEX_COL2

    • VARCHAR FLEX_COL3

次に、「タスク3: 追加マッピングの提供」に説明されているように、拡張された属性を永続化するために、どのフレキシブル列を使用するかを指定できます。

6.2.1.3 タスク3: 追加マッピングの提供

追加マッピングを提供するには、columnおよびaccess-methods属性が指定されたマッピングをeclipselink-orm.xmlファイルに追加します。次に例を示します。

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

6.2.1.4 タスク4: 永続性プロパティおよびデータ・リポジトリの構成

永続性ユニット・プロパティを構成して、アプリケーションがeclipselink-orm.xmlファイルからフレキシブル・マッピングを取得する必要があることを示します。次の項に説明されているように、persistence.xmlを使用するか、EntityManagerFactoryにプロパティを設定して、永続性ユニット・プロパティを設定できます。

外部マッピングの詳細は、EclipseLinkドキュメントの『External Mappings』を参照してください。

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

6.2.1.4.1 persistence.xmlの構成

デフォルトのeclipselink-orm.xmlファイルを使用するには、persistence.xmlファイルでeclipselink.metadata-sourceプロパティを使用します。指定した場所にある別のファイルを使用するには、eclipselink.metadata-source.xml.urlプロパティを使用します。次に例を示します。

<property name="eclipselink.metadata-source" value="XML"/>
<property name="eclipselink.metadata-source.xml.url" value="foo://bar"/>
6.2.1.4.2 EntityManagerFactoryおよびメタデータ・リポジトリの構成

拡張機能は、ブートストラップ時にメタデータ・リポジトリにアクセスして追加されます。メタデータ・リポジトリには、そこに保持されているメタデータを取得するメソッドを提供するクラスを介してアクセスします。現在のリリースには、XMLリポジトリをサポートするメタデータ・リポジトリの実装が含まれています。

メタデータ・リポジトリに対して使用するクラスおよび構成情報を永続性ユニット・プロパティに指定します。エンティティ・マネージャ・ファクトリでは、メタデータ・リポジトリの追加マッピング情報が、ブートストラップに使用されるメタデータに統合されます。

独自のクラスの実装を用意して、メタデータ・リポジトリにアクセスできます。各メタデータ・リポジトリ・アクセス・クラスには、リポジトリに接続するために使用するプロパティのセットを個別に指定する必要があります。

次のクラスのどちらかをサブクラスにできます。

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

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

次の例では、com.fooで始まるプロパティが、開発者によって定義されています。

<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 メタデータ・リポジトリのリフレッシュ

メタデータを変更して、新しいメタデータに基づくEntityManagerが必要な場合は、refreshMetadata()EntityManagerFactoryで呼び出して、データをリフレッシュする必要があります。次のEntityManagerは、新しいメタデータに基づいたものになります。

refreshMetadataメソッドは、プロパティのMapを受け取って、プロパティのそのマップを使用して、metadata-sourceに以前に定義されていたプロパティをオーバーライドできます。

6.2.2 コード例

例6-2は、次のことを示しています。

  • 非拡張フィールドには、フィールド・アクセスが使用されています。

  • 拡張フィールドには、デフォルト(get(String)およびset(String, Object))を使用した仮想アクセスが使用されています。

  • get(String)およびset(String, Object)メソッドは、マッピングで使用されていなくても、@VirtualAccessMethodsが指定されているので、ウィービングされます。

  • 拡張機能は、@Transientを指定して、移植可能な方法でマッピングされています。

例6-2 デフォルトのgetおよびsetメソッド名を使用した仮想アクセス

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

例6-3は、次のことを示しています。

  • 非拡張フィールドには、フィールド・アクセスが使用されています。

  • getおよびsetに使用されるメソッドを@VirtualAccessMethods注釈でオーバーライドしています。

  • get(String)およびset(String, Object)メソッドは、マッピングで使用されていなくても、@VirtualAccessMethodsが指定されているので、ウィービングされます。

  • 拡張機能は、@Transientを指定して、移植可能な方法でマッピングされています。

  • get()およびset()メソッドのどちらを使用するかを、拡張マッピングのXMLで示しています。

例6-3 GetおよびSetメソッドのオーバーライド

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

例6-4は、次のことを示しています。

  • 非拡張フィールドには、プロパティ・アクセスが使用されています。

  • 拡張フィールドには、デフォルト(get(String)およびset(String, Object))を使用した仮想アクセスが使用されています。

  • 拡張機能は移植可能な方法でマッピングされています。プロパティ・アクセスが使用されるので、@Transientは不要です。

  • get(String)およびset(String, Object)メソッドは、マッピングで使用されていなくても、@VirtualAccessMethodsが指定されているので、ウィービングされます。

例6-4 プロパティ・アクセスの使用

@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 JAXB Beanの拡張可能化

@XmlVirtualAccessMethods注釈を使用して、JAXB Beanが拡張可能であることを指定します。拡張可能なBeanで仮想プロパティを使用すれば、Beanの外部でマッピングを指定できます。これにより、Beanのソース・ファイルの変更およびBeanの永続性ユニットの再デプロイをしなくても、マッピングを変更できるようになります。

マルチテナント(またはSaaS)アーキテクチャでは、サーバーで1つのアプリケーションが動作して、複数のクライアント組織(テナント)にサービスを提供します。優れたマルチテナント・アプリケーションでは、テナントごとのカスタマイズが可能です。これらのカスタマイズがデータに対して行われる場合は、バインディング・レイヤーでカスタマイズを処理するのが難しい場合があります。JAXBは、実際のフィールドおよびプロパティがあるドメイン・モデルを操作するように設計されています。EclipseLink Object-XML 2.3 (MOXyとも呼ばれます)には、このユースケースを容易に処理できる、仮想プロパティの概念が導入されています。仮想プロパティはObject-XMLメタデータ・ファイルによって定義されているので、ソースを変更しなくてもクラスを拡張できます。

この項の内容は、次のとおりです。

6.3.1 主な手順

拡張可能なJAXB Beanを作成およびサポートするには:

6.3.1.1 タスク1: Beanの構成

Beanの構成では、@XmlVirtualAccessMethodsによるBeanクラスの注釈指定、プロパティ値に対するgetおよびsetメソッドの追加、および拡張された属性と値をストアするデータ構造の追加をします。

6.3.1.1.1 @Xml VirtualAccessMethodsによるBeanクラスの注釈指定

@XmlVirtualAccessMethodsでBeanに注釈を付けて、Beanが拡張可能であることを指定し、仮想プロパティを定義します。

表6-2は、@XmlVirtualAccessMethods注釈で使用可能な属性を説明しています。

表6-2 @XmlVirtualAccessMethods注釈の属性

属性 説明

get

仮想プロパティに使用するgetterメソッドの名前。このメソッドは、1つのjava.lang.Stringパラメータを受け取ってjava.lang.Objectを返す必要があります。

デフォルト: get

必須かどうか:いいえ

set

仮想プロパティに使用するsetterメソッドの名前。このメソッドは、java.lang.Stringおよびjava.lang.Objectパラメータを受け取ってjava.lang.Objectパラメータを返す必要があります。

デフォルト: set

必須かどうか:いいえ


6.3.1.1.2 Beanに対するgetおよびsetメソッドの追加

Beanに対して、get(String)およびset(String, Object)メソッドを追加します。get()メソッドは、プロパティ名によって値を返し、set()メソッドはプロパティ名によって値をストアします。これらのメソッドのデフォルト名は、getおよびsetで、@XmlVirtualAccessMethods注釈でオーバーライドできます。

EclipseLinkでは、ウィービングが有効な場合に、これらのメソッドがウィービングされて、遅延ロード、変更追跡、フェッチ・グループおよび内部最適化がサポートされます。

6.3.1.1.3 データ構造の追加

拡張された属性と値、つまり仮想マッピングをストアするデータ構造を追加します。その後、これらをデータベースにマップできます。「タスク2: 追加マッピングの提供」を参照してください。

仮想マッピングはMapにストアするのが一般的ですが、他の方式を使用することもできます。たとえば、ディレクトリ・システムに仮想マッピングをストアすることもできます。

フィールドベースのアクセスを使用する際には、@XmlTransientでデータ構造に注釈を付けて、この構造を別のマッピングに使用できないようにします。プロパティベースのアクセスを使用する場合、@XmlTransientは不要です。

6.3.1.1.4 XMLの使用

@XmlVirtualAccessMethodsを使用する方法に加えて、代替方法として、<access>および<access-methods>要素を、次のように使用することもできます。

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

getおよびsetを使用する仮想アクセス・メソッドを有効にするXML:

<xml-virtual-access-methods/>

setではなくputを使用する仮想アクセス・メソッドを有効にするXML(デフォルト):

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

getではなくretrieveを使用する仮想アクセス・メソッドを有効にするXML(デフォルト):

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

getsetではなくretrieveputを使用する仮想アクセス・メソッドを有効にするXML(デフォルト):

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

6.3.1.2 タスク2: 追加マッピングの提供

追加マッピングを提供するには、マッピングをeclipselink-oxm.xmlファイルに追加します。次に例を示します。

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

6.3.2 コード例

この項の例は、拡張可能なJAXB Beanを使用する方法を示します。この例では、他のクラスが拡張できるベース・クラスを最初に作成します。この場合、拡張可能なクラスは、CustomersおよびPhoneNumbers用です。マッピング・ファイルは2つの個別のテナントに対して作成されています。両方のテナントはいくつかの実際のプロパティを共有していますが、それぞれの要件に固有の仮想プロパティを定義します。

6.3.2.1 基本設定

例6-5は、他の拡張可能なクラスが拡張できるベース・クラスExtensibleBaseを示しています。この例では、@XmlTransient注釈を使用することにより、ExtensibleBaseが継承関係としてマップされないようにしています。モデル内で、すべてのテナントに共通な部分は、実際のプロパティで表されます。テナントごとの拡張機能は、仮想プロパティとして表されます。

例6-5 拡張可能なクラスのベース・クラス

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);
    }
}

例6-6に、Customerクラスの定義を示します。Customerクラスは、@XmlVirtualAccessMethods注釈が付けられたドメイン・クラスから継承するので拡張可能です。

例6-6 拡張可能なCustomerクラス

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

例6-7に、Addressクラスを示します。モデル内のすべてのクラスを拡張可能にする必要はありません。この例のAddressクラスには、仮想プロパティはありません。

例6-7 拡張可能ではないAddressクラス

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

例6-8に、PhoneNumberクラスを示します。Customerと同様に、PhoneNumberは拡張可能なクラスになります。

例6-8 拡張可能なPhoneNumberクラス

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 テナントの定義

この項の例では、2つの個別のテナントが定義されています。両方のテナントはいくつかの実際のプロパティを共有していますが、仮想プロパティが指定されているので、対応するXML表現はまったく異なるものにできます。

テナント1

最初のテナントは、オンライン・スポーツ用品店で、次のようにモデルを拡張する必要があります。

  • 顧客ID

  • 顧客のミドル・ネーム

  • 配送先住所

  • 連絡先電話番号のコレクション

  • 電話番号のタイプ(自宅、勤務先、または携帯など)

仮想プロパティのメタデータは、Object-XMLのXMLマッピング・ファイルを介して指定します。仮想プロパティは、実際のプロパティと同様の方法でマップされます。タイプ(リフレクションでは特定できないため)、およびコレクション・プロパティの場合はコンテナ・タイプなど、追加情報がいくらか必要です。次に定義されているCustomerの仮想プロパティは、middleNameshippingAddressおよびphoneNumbersです。PhoneNumberの仮想プロパティはtypeプロパティです。

例6-9に、binding-tenant1.xmlマッピング・ファイルを示します。

例6-9 テナント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>

getおよびsetメソッドは、実際のプロパティを操作するためにドメイン・モデルで使用し、@XmlVirtualAccessMethods注釈に定義されているアクセッサは、仮想プロパティを操作するために使用します。通常のJAXBメカニズムは、操作のマーシャルおよびアンマーシャルに使用します。例6-10に、仮想プロパティに関連付けられているデータを取得するための、テナント1のCustomerクラスのコードを示します。

例6-10 仮想プロパティに関連付けられているデータを提供するためのテナント1のコード

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

例6-11に、テナント1のCustomerクラスからのXML出力を示します。

例6-11 テナント1のCustomerクラスからのXML出力

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

テナント2

2番目のテナントは、オンデマンドの映画や音楽を提供するストリーミング・メディア・プロバイダです。ここでは、コア・モデルに対して別のセットの拡張機能が必要になります。

  • 1つの連絡先電話番号

このテナントの場合、実際のプロパティのマッピングのカスタマイズにもマッピング・ファイルを使用します。

例6-12に、binding-tenant2.xmlマッピング・ファイルを示します。

例6-12 テナント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>

例6-13に、仮想プロパティに関連付けられているデータを取得するための、テナント2のCustomerクラスのコードを示します。

例6-13 仮想プロパティに関連付けられているデータを提供するためのテナント2のコード

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

例6-14に、テナント2のCustomerクラスからのXML出力を示します。

例6-14 テナント2のCustomerクラスからのXML出力

<?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 単一表マルチテナントの使用

SaaSを実装するため重要な要素は、テナントが自分のデータのみを操作できるようにしながら、複数のアプリケーション・テナントが共有の永続性スキーマを使用できるようにすることです。単一表マルチテナントでは、すべてのアプリケーション・テナントに対して1つの表を使用し、特定のアプリケーション・コンテキスと値が設定されたテナント識別子列に基づいてアプリケーション・テナントを区別します。アプリケーションでは、必要な数の識別子列を構成することも、デフォルト動作を使用することもできます。単一表マルチテナントの詳細は、次を参照してください。

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

この項の内容は次のとおりです。

6.4.1 主なタスク

この項のタスクでは、SaaS環境で動作するように設計されたアプリケーションを作成する際に、単一表マルチテナントを使用する手順を説明します。

この項のタスクは次のとおりです。

6.4.1.1 タスク1: 単一表マルチテナントの有効化

単一表マルチテナントは、@Multitenant注釈またはORM XMLファイルの<multitenant>要素を使用するか、注釈とXMLを一緒に使用して、宣言することにより有効にできます。

@Multitenant注釈の使用

@Multitenant注釈を使用する場合は、@Entityまたは@MappedSuperclass注釈とともにこの注釈を指定して、SINGLE_TABLE属性を指定します。次に例を示します。

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

SINGLE_TABLE属性は、指定されたエンティティに関連付けられている表(TableおよびSecondaryTable)をテナント間で共有できることを示します。

<multitenant>要素の使用

<multitenant>要素を使用する場合は、<entity>要素内にこの要素を指定します。次に例を示します。

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

6.4.1.2 タスク2: テナント識別子列の指定

識別子列は、関連付けられているアプリケーション・コンテキストとともに使用して、表のどの行にアプリケーション・テナントがアクセスできるかを示します。複数のテナント識別子列を指定できます。テナント識別子列を指定しないと、TENANT_IDという名前のデフォルト列が、デフォルトのeclipselink.tenant-idコンテキスト・プロパティとともに使用されます。表またはセカンダリ表を明示的に指定しないかぎり、テナント識別子列はプライマリ表に存在すると仮定されます。デフォルト動作を変更する方法は、次を参照してください。

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

テナント識別子列は、@TenantDiscriminatorColumnまたは@TenantDiscriminatorColumns注釈を使用するか、ORM XMLファイルで<tenant-discriminator-column>要素を使用して、宣言することにより指定できます。

@TenantDiscriminatorColumn注釈の使用

@TenantDiscriminatorColumn注釈を使用する場合は、@Entityまたは@MappedSuperclass注釈とともにこの注釈を指定して、nameおよびcontextProperty属性を指定します。次に例を示します。

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

複数の列を指定する場合は、複数の@TenantDiscriminatorColumn注釈を@TenantDiscriminatorColumns注釈内に指定し、プライマリ表に列がない場合は、列がある表を指定します。次に例を示します。

@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>要素の使用

<tenant-discriminator-column>要素を使用する場合は、<multitenant>要素内にこの要素を指定して、nameおよびcontext-property属性を指定します。次に例を示します。

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

複数の列を指定する場合は、追加の<tenant-discriminator-column>要素を指定して、プライマリ表に列がない場合は、列がある表を指定します。次に例を示します。

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

テナント識別子列のマッピング

テナント識別子列は、主キーまたは別の列にマッピングできます。次の例では、DDL生成時に、表の主キーにテナント識別子列をマッピングしています。

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

オブジェクト・エンティティの一部として、識別子列が主キーにマップされるようにするには、列をマップする必要があります。次に例を示します。

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

次の例では、ORM XMLファイルの主キーにテナント識別子列をマッピングしています。

<entity class="model.Address">
   <multitenant>
      <tenant-discriminator-column name="TENANT"
         context-property="multi-tenant.id" primary-key="true"/>
   </multitenant>
   <table name="ADDRESS"/>
   ...
</entity>

次の例では、別の列名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;
}

また、ORM XMLファイルでは、次のようになります。

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

実行時のコンテキスト・プロパティの指定

実行時には、create entity manager factoryコールに渡される永続性ユニット定義を使用するか、個々のエンティティ・マネージャに設定して、コンテキスト・プロパティ構成を指定できます。次に例を示します。

<persistence-unit name="multi-tenant">
   ...
   <properties>
      <property name="tenant.id" value="707"/>
      ...
   </properties>
</persistence-unit>

または、コードで次のように記述することもできます。

HashMap properties = new HashMap();
properties.put(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, "707");
EntityManager em = Persistence.createEntityManagerFactory("multi-tenant-pu",
   properties).createEntityManager();

エンティティ・マネージャのプロパティ定義は、次のとおりです。

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 タスク3: 実行時の識別子列の使用

テナント識別子列は、エンティティ・マネージャの操作および問合せを介して、実行時に使用できます。テナント識別子列と値は、次のエンティティ・マネージャの操作でサポートされます。

  • persist()

  • find()

  • refresh()

テナント識別子列と値は、次のエンティティ・マネージャの問合せでサポートされます。

  • 名前付き問合せ

  • すべて更新

  • すべて削除


注意:

マルチテナントは、名前付きのネイティブ問合せではサポートされません。マルチテナント環境で名前付きのネイティブ問合せを使用する場合は、マルチテナントに関する問題を問合せで直接に手動で処理してください。一般に、マルチテナント環境では名前付きのネイティブ問合せを使用しないことをお薦めします。


6.4.2 その他の参考資料

次の参照資料を使用できます。

6.4.2.2 関連Javadoc

詳細は、『Oracle Fusion Middleware Java API Reference for EclipseLink』の次のAPIを参照してください。

  • org.eclipse.persistence.annotations.Multitenant

  • org.eclipse.persistence.annotations.TenantDiscriminatorColumn

  • org.eclipse.persistence.annotations.TenantDiscriminatorColumns

6.5 外部メタデータ・ソースの使用

TopLinkでは、実行中のアプリケーションの外部にあるメタデータ・ソースにマッピング情報をストアできます。マッピング情報は、アプリケーションが永続性ユニットを作成するときに取得されるので、デプロイされたアプリケーションで動的にマッピングのオーバーライドまたは拡張を行えます。

6.5.1 eclipselink-orm.xmlファイルの外部使用

TopLinkでは、eclipselink-orm.xmlファイルを使用し、高度なマッピングのタイプおよびオプションをサポートできます。このファイルで、標準のJPA orm.xmlマッピング構成ファイルをオーバーライドできます。

6.5.2 主なタスク

この項には、次の項目が含まれます。

6.5.2.1 タスク1: 永続性ユニットの構成

永続性ユニットを構成して、次の方法で外部メタデータを使用できます。

6.5.2.1.1 固定場所へのアクセス

追加のマッピング情報が指定されたeclipselink-orm.xmlファイルなどの外部ファイルにアクセスする最も簡単な方法は、Webサーバー上の固定URLでファイルを使用できるようにすることです。

例6-15に示すeclipselink.metadata-source.xml.urlプロパティを使用して、場所を指定します。

例6-15 固定場所

<property name="eclipselink.metadata-source" value="XML"/>
<property name="eclipselink.metadata-source.xml.url" value="http://myserverlocation/"/>
6.5.2.1.2 アプリケーション・コンテキスト・ベースの場所へのアクセス

マルチテナント・アプリケーションでテナント固有の拡張機能を提供する場合のように、要件がさらに複雑な場合は、アプリケーション・コンテキストに基づいて外部メタデータの場所を指定できます。

例6-16に示すMetadataSourceインタフェースを実装して、場所を指定します。

例6-16 固定場所

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

例6-17に、テナントに基づいて、特定のマッピング・ファイルを返す方法を示します。

例6-17 テナント固有のマッピング・ファイル

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 タスク2: サーバーの構成

メタデータ・ファイルにアクセスするには、次のいずれかの方法を使用して、マッピング・ファイルへのURLアクセスをサーバーで実現する必要があります。

  • 静的なファイル提供サービス

  • 自分のマッピング・ファイル、またはストアされているマッピング情報からオンデマンドで作成されるマッピング・ファイルを使用するサーバーベースのソリューション

  • その他のWebテクノロジ

6.5.3 その他の参考資料

JPAデプロイメントの詳細は、JPA仕様の次の項(http://jcp.org/en/jsr/detail?id=317)を参照してください。

  • 7.2項「Bootstrapping in Java SE Environments」

  • 第7章「Container and Provider Contracts for Deployment and Bootstrapping」

6.5.3.1 Javadoc

詳細は、『Oracle Fusion Middleware Java API Reference for EclipseLink』の次のAPIを参照してください。

  • PersistenceUnitPropertiesクラス