この章では、JPAエンティティおよびJAXB Beanを拡張可能にする方法を説明します。エンティティまたはBeanのソース・ファイルを変更したり、永続性ユニットの再デプロイをしなくても、外部でマッピングを追加したり変更できます。この機能は、複数のクライアントがアプリケーションおよびデータ・ソースを共有するSoftware-as-a-Service環境で便利です。また、デプロイ時ではなく、インストール時にアプリケーションをカスタマイズする際にも便利です。
この章の内容は次のとおりです。
ユース・ケース
アプリケーションおよびデータ・ソースが複数のクライアントにより共有されるSaaS環境をユーザーが構築したいと考えています。
解決方法
TopLinkの拡張機能を使用し、外部マッピングを使用してJPAエンティティおよびJAXB Beanを拡張します。
コンポーネント
TopLink 12cリリース1 (12.1.2)以上。
|
注意: TopLinkのコア機能は、オープン・ソースのEclipse Foundationの永続性フレームワークであるEclipseLinkによって提供されています。EclipseLinkでは、Java Persistence API (JPA)、Java Architecture for XML Binding (JAXB)、および標準に基づいたその他の永続性テクノロジと、それらの標準の拡張が実装されます。TopLinkには、EclipseLinkのすべてに加え、Oracleの追加機能が含まれています。 |
@VirtualAccessMethods注釈を使用して、エンティティが拡張可能であることを指定します。拡張可能なエンティティで仮想プロパティを使用すれば、エンティティの外部でマッピングを指定できます。これにより、エンティティのソース・ファイルの変更およびエンティティの永続性ユニットの再デプロイをしなくても、マッピングを変更できるようになります。
拡張可能なエンティティは、複数のクライアント(テナント)が共有の汎用アプリケーションを使用するマルチテナント(SaaS)アーキテクチャで便利です。テナントは、自分のデータにプライベートにアクセスでき、他のテナントと共有するデータにもアクセスできます。
拡張可能なエンティティを使用すれば、次のことを行えます。
一部のマッピングがすべてのユーザーに共通で、一部のマッピングはユーザー固有であるようなアプリケーションを作成できます。
顧客に提供された後のアプリケーションにマッピングを追加できます(デプロイメント後でも可能)。
マッピングの変更後も、同じEntityManagerFactoryインタフェースを使用してデータを操作できます。
アプリケーションで使用されるメタデータのソースを追加提供できます。
拡張可能なJPAエンティティを作成およびサポートするには:
エンティティを構成するには、次の項で説明するとおり、エンティティ・クラスを@VirtualAccessMethodsで注釈指定し(またはXML <access-methods>を使用し)、プロパティ値にgetおよびsetメソッドを追加し、拡張した属性と値をストアするデータ構造を追加します。
@VirtualAccessMethodsでエンティティに注釈を付けて、エンティティが拡張可能であることを指定し、仮想プロパティを定義します。
表12-1は、@VirtualAccessMethods注釈で使用可能な属性を説明しています。
表12-1 @VirtualAccessMethods注釈の属性
| 属性 | 説明 |
|---|---|
|
|
仮想プロパティに使用する デフォルト: 必須かどうか: いいえ |
|
|
仮想プロパティに使用する デフォルト: 必須かどうか: いいえ |
エンティティに対して、get(String)およびset(String, Object)メソッドを追加します。get()メソッドは、プロパティ名によって値を返し、set()メソッドはプロパティ名によって値をストアします。これらのメソッドのデフォルト名は、getおよびsetで、@VirtualAccessMethods注釈でオーバーライドできます。
EclipseLinkでは、ウィービングが有効な場合に、これらのメソッドがウィービングされて、遅延ロード、変更追跡、フェッチ・グループおよび内部最適化がサポートされます。
|
注意:
|
拡張された属性と値、つまり仮想マッピングをストアするデータ構造を追加します。その後、これらをデータベースにマップできます。12.1.1.3項「タスク3: 追加マッピングの提供」を参照してください。
仮想マッピングは(例12-1に示されているような)Mapオブジェクトにストアするのが一般的ですが、他の戦略を使用することもできます。
フィールドベースのアクセスを使用する際には、@Transientでデータ構造に注釈を付けて、この構造を別のマッピングに使用できないようにします。プロパティベースのアクセスを使用する場合、@Transientは不要です。
例12-1に、プロパティ・アクセスを使用するエンティティ・クラスを示します。
例12-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);
}
@VirtualAccessMethods注釈を使用する方法に加えて、代替方法として、たとえば次などのように、(<basic>などの)マッピング要素にaccess="VIRTUAL"を使用できます。
<basic name="idNumber" access="VIRTUAL" attribute-type="String">
<column name="FLEX_COL1"/>
</basic>
永続性ユニットのデフォルトとして仮想アクセス方法を設定するには、たとえば次などのように<access>および<access-methods>要素を使用します。
<persistence-unit-metadata>
<xml-mapping-metadata-complete/>
<exclude-default-mappings/>
<persistence-unit-defaults>
<access>VIRTUAL</access>
<access-methods set-method="get" get-method="set"/>
</persistence-unit-defaults>
</persistence-unit-metadata>
データベース表に仮想属性値をストアする追加の列を用意します。たとえば、次のCustomer表には、事前定義されたIDとNAMEの2つの列があり、属性値をストアするEXT_1、EXT_2、EXT_3の3つの列があります。
CUSTOMER表
INTEGER ID
VARCHAR NAME
VARCHAR EXT_1
VARCHAR EXT_2
VARCHAR EXT_3
次に、「タスク3: 追加マッピングの提供」に説明されているように、拡張された属性を永続化するために、どのFLEX列を使用するかを指定できます。
追加マッピングを提供するには、columnおよびaccess-methods属性が指定されたマッピングをeclipselink-orm.xmlファイルに追加します。次に例を示します。
<basic name="idNumber" access="VIRTUAL" attribute-type="String"> <column name="FLEX_COL1"/> </basic>
永続性ユニット・プロパティを構成して、アプリケーションがeclipselink-orm.xmlファイルからフレキシブル・マッピングを取得する必要があることを示します。次の項に説明されているように、persistence.xmlファイルを使用するか、EntityManagerFactoryインタフェースにプロパティを設定して、永続性ユニット・プロパティを設定できます。
外部マッピングの詳細は、13章「外部メタデータ・ソースの使用」を参照してください。
デフォルトの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"/>
拡張機能は、ブートストラップ時にメタデータ・リポジトリにアクセスして追加されます。メタデータ・リポジトリには、そこに保持されているメタデータを取得するメソッドを提供するクラスを介してアクセスします。EclipseLinkには、XMLリポジトリをサポートするメタデータ・リポジトリが実装されています。
メタデータ・リポジトリに対して使用するクラスおよび構成情報を永続性ユニット・プロパティに指定します。EntityManagerFactoryインタフェースでは、メタデータ・リポジトリの追加マッピング情報が、ブートストラップに使用されるメタデータに統合されます。
独自のクラスの実装を用意して、メタデータ・リポジトリにアクセスできます。各メタデータ・リポジトリ・アクセス・クラスには、リポジトリに接続するために使用するプロパティのセットを個別に指定する必要があります。
次のクラスのどちらかをサブクラスにできます。
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"/>
例12-2は、次のことを示しています。
非拡張フィールドには、フィールド・アクセスが使用されています。
拡張フィールドには、デフォルト(get(String)およびset(String, Object))を使用した仮想アクセスが使用されています。
get(String)およびset(String, Object)メソッドは、マッピングで使用されていなくても、@VirtualAccessMethodsが指定されているので、ウィービングされます。
これらの項目は、太字のフォントで示します。
例12-2 デフォルトのgetおよびsetメソッド名を使用した仮想アクセス
@Entity@VirtualAccessMethodspublic class Address { @Id private int id;@Transientprivate 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);} . . .
例12-3は、次のことを示しています。
非拡張フィールドには、フィールド・アクセスが使用されています。
getおよびsetに使用されるメソッドを@VirtualAccessMethods注釈でオーバーライドしています。
get(String)およびset(String, Object)メソッドは、マッピングで使用されていなくても、@VirtualAccessMethodsが指定されているので、ウィービングされます。
get()およびset()メソッドのどちらを使用するかを、拡張マッピングのXMLで示しています。
これらの項目は、太字のフォントで示します。
例12-3 getおよびsetメソッドのオーバーライド
@Entity@VirtualAccessMethods(get="getExtension", set="setExtension")public class Address { @Id private int id;@Transientprivate 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" access="VIRTUAL" attribute-type="String"><column name="FLEX_1"/></basic>
例12-4は、次のことを示しています。
非拡張フィールドには、プロパティ・アクセスが使用されています。
拡張フィールドには、デフォルト(get(String)およびset(String, Object))を使用した仮想アクセスが使用されています。
拡張機能は移植可能な方法でマッピングされています。プロパティ・アクセスが使用されるので、@Transientは不要です。
get(String)およびset(String, Object)メソッドは、マッピングで使用されていなくても、@VirtualAccessMethodsが指定されているので、ウィービングされます。
これらの項目は、太字のフォントで示します。
例12-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);} ...
@XmlVirtualAccessMethods注釈を使用して、JAXB Beanが拡張可能であることを指定します。拡張可能なBeanで仮想プロパティを使用すれば、Beanの外部でマッピングを指定できます。これにより、Beanのソース・ファイルの変更およびBeanの永続性ユニットの再デプロイをしなくても、マッピングを変更できるようになります。
マルチテナント(またはSaaS)アーキテクチャでは、サーバーで1つのアプリケーションが動作して、複数のクライアント組織(テナント)にサービスを提供します。優れたマルチテナント・アプリケーションでは、テナントごとのカスタマイズが可能です。これらのカスタマイズがデータに対して行われる場合は、バインディング・レイヤーでカスタマイズを処理するのが難しい場合があります。JAXBは、実際のフィールドおよびプロパティがあるドメイン・モデルを操作するように設計されています。EclipseLinkのJAXBに対する拡張には、このユース・ケースを簡単に扱う仮想プロパティの概念が導入されています。仮想プロパティはObject-XMLメタデータ・ファイルによって定義されているので、ソースを変更しなくてもクラスを拡張できます。
この項の内容は、次のとおりです。
拡張可能なJAXB Beanを作成およびサポートするには:
Beanを構成するには、Beanクラスを@XmlVirtualAccessMethodsで注釈指定し、プロパティ値にgetおよびsetメソッドを追加し、拡張した属性と値をストアするデータ構造を追加します。または、eclipselink-orm.xmlで<xml-virtual-access-methods>要素を使用します。
@XmlVirtualAccessMethodsでBeanに注釈を付けて、Beanが拡張可能であることを指定し、仮想プロパティを定義します。
表12-2は、@XmlVirtualAccessMethods注釈で使用可能な属性を説明しています。
表12-2 @XmlVirtualAccessMethods注釈の属性
| 属性 | 説明 |
|---|---|
|
|
仮想プロパティに使用するgetterメソッドの名前。このメソッドは、1つの デフォルト: 必須かどうか: いいえ |
|
|
仮想プロパティに使用するsetterメソッドの名前。このメソッドは、 デフォルト: 必須かどうか: いいえ |
Beanに対して、get(String)およびset(String, Object)メソッドを追加します。get()メソッドは、プロパティ名によって値を返し、set()メソッドはプロパティ名によって値をストアします。これらのメソッドのデフォルト名は、getおよびsetで、@XmlVirtualAccessMethods注釈でオーバーライドできます。
拡張された属性と値、つまり仮想マッピングをストアするデータ構造を追加します。その後、これらをデータベースにマップできます。「タスク2: 追加マッピングの提供」を参照してください。
仮想マッピングはMapにストアするのが一般的ですが、他の方式を使用することもできます。たとえば、ディレクトリ・システムに仮想マッピングをストアすることもできます。
フィールドベースのアクセスを使用する際には、@XmlTransientでデータ構造に注釈を付けて、この構造を別のマッピングに使用できないようにします。プロパティベースのアクセスを使用する場合、@XmlTransientは不要です。
@XmlVirtualAccessMethodsを使用する方法に加えて、代替方法として、同等のXMLを、次のように使用することもできます。
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"/>
getとsetではなくretrieveとputを使用する仮想アクセス・メソッドを有効にするXML(デフォルト):
<xml-virtual-access-methods get-method="retrieve" set-method="put"/>
追加マッピングを提供するには、マッピングをeclipselink-oxm.xmlファイルに追加します。次に例を示します。
<xml-element java-attribute="idNumber"/>
この項の例は、拡張可能なJAXB Beanを使用する方法を示します。この例では、他のクラスが拡張できるベース・クラスを最初に作成します。この場合、拡張可能なクラスは、CustomersおよびPhoneNumbers用です。マッピング・ファイルは2つの個別のテナントに対して作成されています。両方のテナントはいくつかの実際のプロパティを共有していますが、それぞれの要件に固有の仮想プロパティを定義します。
例12-5は、他の拡張可能なクラスが拡張できるベース・クラスExtensibleBaseを示しています。この例では、@XmlTransient注釈を使用することにより、ExtensibleBaseが継承関係としてマップされないようにしています。モデル内で、すべてのテナントに共通な部分は、実際のプロパティで表されます。テナントごとの拡張機能は、仮想プロパティとして表されます。
例12-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); } }
例12-6に、Customerクラスの定義を示します。Customerクラスは、@XmlVirtualAccessMethods注釈が付けられたドメイン・クラスから継承するので拡張可能です。
例12-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;
}
}
例12-7に、Addressクラスを示します。モデル内のすべてのクラスを拡張可能にする必要はありません。この例のAddressクラスには、仮想プロパティはありません。
例12-7 拡張可能ではないAddressクラス
package examples.virtual;
public class Address {
private String street;
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
}
例12-8に、PhoneNumberクラスを示します。Customerと同様に、PhoneNumberは拡張可能なクラスになります。
この項の例では、2つの個別のテナントが定義されています。両方のテナントはいくつかの実際のプロパティを共有していますが、仮想プロパティが指定されているので、対応するXML表現はまったく異なるものにできます。
テナント1
最初のテナントは、オンライン・スポーツ用品店で、次のようにモデルを拡張する必要があります。
顧客ID
顧客のミドル・ネーム
配送先住所
連絡先電話番号のコレクション
電話番号のタイプ(自宅、勤務先、または携帯など)
仮想プロパティのメタデータは、eclipselink-oxm.xmlマッピング・ファイルまたはeclipselink-orm.xmlスキーマを使用するファイルにキャプチャされます。仮想プロパティは、実際のプロパティと同様の方法でマップされます。タイプ(リフレクションでは特定できないため)、およびコレクション・プロパティの場合はコンテナ・タイプなど、追加情報がいくらか必要です。次に定義されているCustomerの仮想プロパティは、middleName、shippingAddressおよびphoneNumbersです。PhoneNumberの仮想プロパティはtypeプロパティです。
例12-9に、binding-tenant1.xmlマッピング・ファイルを示します。
例12-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メカニズムは、操作のマーシャルおよびアンマーシャルに使用します。例12-10に、仮想プロパティに関連付けられているデータを取得するための、テナント1のCustomerクラスのコードを示します。
例12-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);
...
例12-11に、テナント1のCustomerクラスからのXML出力を示します。
例12-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つの連絡先電話番号
このテナントの場合、実際のプロパティのマッピングのカスタマイズにもマッピング・ファイルを使用します。
例12-12に、binding-tenant2.xmlマッピング・ファイルを示します。
例12-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>
例12-13に、仮想プロパティに関連付けられているデータを取得するための、テナント2のCustomerクラスのコードを示します。
例12-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);
...
例12-14に、テナント2のCustomerクラスからのXML出力を示します。
この章のソリューションが実装されているその他のテクノロジおよびツールの詳細は、次の参考資料を参照してください。
コード例
『Oracle TopLink Java Persistence API (JPA)拡張機能リファレンス』の@VirtualAccessMethods
Oracle TopLinkによるJAXBアプリケーションの開発の仮想アクセスの方法の定義に関する項