15 XMLへのJPAのマッピング
この章には次のトピックが含まれます:
ユース・ケース
JPAエンティティをユーザーがXMLにマップする必要があります。
解決方法
TopLinkでは、EclipseLinkのJAXBの拡張を介して、JAXB標準をサポートしています。
EclipseLinkでは、EclipseLinkのMOXyの拡張を介して、JAXB標準をサポートしています。
コンポーネント
-
TopLink 12c (12.1.2.0.0)以上。
注意:
TopLinkのコア機能は、オープン・ソースのEclipse Foundationの永続性フレームワークであるEclipseLinkによって提供されています。EclipseLinkでは、Java Persistence API (JPA)、Java Architecture for XML Binding (JAXB)、および標準に基づいたその他の永続性テクノロジと、それらの標準の拡張が実装されます。TopLinkには、EclipseLinkのすべてに加え、Oracleの追加機能が含まれています。
-
EclipseLink 2.4以降。
-
XML文書
サンプル
関連情報については、次のEclipseLinkおよびJAXBの例を参照してください。
ソリューションの概要
この章では、XMLにJPAエンティティをマッピングする一般的な方法をいくつか示します。次の例を扱うには、JAXB、MOXy、XMLバインディング、JAXB注釈のオーバーライド方法などのJPAからXMLへのマッピングの全体的な概念を理解している必要があります。次の項では、これらの概念の基礎的なことがらを説明します。
XMLバインディングの理解
XMLバインディングとは、コンピュータのメモリーのオブジェクトとして、XML文書内に情報を表現する方法です。これにより、Domain Object Model (DOM)、Simple API for XML (SAX)またはStreaming API for XML (StAX)を使用してXML自身の直接表現からデータを取得しなくても、オブジェクトからXML内のデータにアクセスできるようになります。バインディングする際に、JAXBでは、JPAエンティティのグラフにツリー構造が適用されます。グラフの複数ツリー表現は可能で、選択されたルート・オブジェクトおよびリレーションシップがトラバースする方向に依存します。
JAXBによるXMLバインディングの例は「XMLへのJPAエンティティのバインディング」にあります。
JAXBの理解
JAXBは、XML文書をJava形式でJavaプログラムに提示することにより、JavaプログラムがXML文書にアクセスできようにするJava APIです。このプロセスはバインディングと呼ばれ、コンピュータ・メモリー内のオブジェクトとして、XML文書内に情報が表現されます。これにより、Domain Object Model (DOM)またはStreaming API for XML (SAX)を使用してXML自身の直接表現からデータを取得しなくても、オブジェクトからXML内のデータにアクセスできるようになります。通常、XMLバインディングをJPAエンティティとともに使用し、JAX-WSまたはJAX-RSの実装を利用して、データ・アクセス・サービスを作成します。これら両方のWebサービス標準では、JAXBがデフォルトのバインディング・レイヤーとして使用されています。このサービスにより、クライアント・コンピュータでJavaが使用されている場合とそうでない場合がある複数のコンピュータ間で、JPAによって公開されたデータにアクセスできるようになります。
JAXBでは、注釈の拡張セットを使用して、JavaからXMLへのマッピングのバインディング・ルールを定義します。これらの注釈は、EclipseLink APIのjavax.xml.bind.
*
パッケージのサブクラスになっています。これらの注釈の詳細は、Oracle TopLink Java APIリファレンスを参照してください。
JAXBの詳細は、次の『Java Architecture for XML Binding (JAXB)』を参照してください。
MOXyの理解
MOXyはEclipseLinkにおけるJAXBの実装です。これにより、POJOモデルをXMLスキーマにマップできるようになるので、JPAからXMLへのマッピングを作成する能力が大幅に拡張します。MOXyでは、javax.xml.bind.annotation
のすべての標準JAXB注釈のサポートに加えて、org.eclipse.persistence.oxm.annotations
パッケージの独自の拡張機能も提供されています。これら後者の注釈を標準の注釈とともに使用すれば、JAXBの実用性をさらに拡張できます。MOXyは最適化されたJAXB実装なので、拡張機能を明示的に使用してもしなくても実装できます。MOXyには、次の利点があります。
-
独自のクラスを独自のXMLスキーマにマップできるようになります。このプロセスを「中間突合せマッピング」と呼びます。これにより、マップされたクラスを1つのXMLスキーマに静的にカップリングしなくてすみます。
-
Xpathベースのマッピング、JSONバインディング、複合キー・マッピングやバックポインタとのリレーションシップのマッピングなど、JPAからXMLへのマッピングの重要な問題に対処するための固有の機能が用意されています。
-
既存のJPAモデルを業界標準のスキーマにマップできます。
-
MOXyマッピングとEclipseLinkの永続性フレームワークを組合せ、JCA経由でデータを対話的に処理できます。
-
数種類のシナリオで優れたパフォーマンスが得られます。
MOXyの詳細は、次の場所にあるMOXyのFAQを参照してください。
XMLデータ表現の理解
JPAからXMLへのマッピングに注釈を使用することは、常に最も効果的とはかぎりません。たとえば、次の場合にはJAXBを使用しません。
-
サード・パーティのクラスにメタデータを指定する必要があるが、ソースにアクセスできない場合。
-
複数のXMLスキーマにオブジェクト・モデルをマップする必要がある場合。JAXBのルールでは、注釈を使用して複数のマッピングを適用することはできません。
-
オブジェクト・モデルにすでに多くの注釈(たとえば、JPA、Spring、JSR-303などのサービスから)が含まれていて、他の場所でメタデータを指定する必要がある場合。
これらの状況および類似の状況では、eclipselink_oxm.xml
ファイルを公開して、XMLデータ表現を使用できます。
XMLメタデータは、次の2つのモードで動作します。
-
注釈で指定されたメタデータに追加します。これは、次の場合に便利です。
-
XML表現のバージョンのひとつが注釈に定義されており、XMLメタデータを使用して将来のバージョンのメタデータを微調整する場合。
-
標準のJAXB注釈を使用し、かつMOXy拡張機能用のXMLメタデータを使用する場合。このようにすると、コンパイル時の依存関係がオブジェクト・モデルに新たに生じることはありません。
-
-
注釈メタデータを完全に置換します。これは、様々なXML表現にマップする必要がある場合に便利です。
XMLデータ表現の使用方法の詳細は、「XMLメタデータ表現を使用したJAXB注釈のオーバーライド」を参照してください
XMLへのJPAエンティティのバインディング
次の例は、JAXB注釈を使用してJPAエンティティをXMLにバインドする方法を示しています。バインディングの詳細は、「XMLバインディングの理解」を参照し、JAXBの詳細は、「JAXBの理解」を参照してください
XMLへのJPAリレーションシップのバインディング
次の演習問題は、JAXBを使用して一連のJPAエンティティからXML表現を導出する方法を示しています。このプロセスは「バインディング」と呼ばれます(XMLバインディングの詳細は、「XMLへのJPAエンティティのバインディング」を参照してください)。これらの例は、次の2つの一般的なJPAリレーションシップをバインドする方法を示しています。
-
私有のリレーションシップ
-
共有参照のリレーションシップ
従業員エンティティを、その従業員の電話番号、住所および部門にマッピングしています。
タスク1: アクセッサ・タイプの定義およびクラスのインポート
次のすべての例で、同じアクセッサ・タイプFIELD
を使用しているので、JAXB注釈@XmlAccessorType
を使用してパッケージ・レベルでアクセッサ・タイプを定義します。この時点で、必要なクラスもインポートします。
@XmlAccessorType(XmlAccessType.FIELD) package com.example.model; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType;
タスク2: 私有のリレーションシップのマッピング
「私有」のリレーションシップは、ターゲット・オブジェクトが1つのソース・オブジェクトのみによって参照されている場合に発生します。このタイプのリレーションシップは、1対1および埋込み、または1対多のどちらの場合にも可能です。
このタスクでは、Employee
エンティティと、Address
およびPhoneNumber
エンティティの間のこれら両方のタイプのリレーションシップに対して双方向のマッピングを作成する方法を示します。
1対1および埋込みのリレーションシップのマッピング
JPAの@OneToOne
および@Embedded
注釈は、ソース・エンティティの1つのインスタンスのみが、同じターゲット・エンティティ・インスタンスを参照できることを示しています。この例は、Employee
エンティティをAddress
エンティティにマップする方法、およびその逆にマップする方法を示しています。従業員は1つの住所のみに関連付けられるので、1対1マッピングと見なされます。このリレーションシップは双方向、つまりEmployee
はEmployee
側からも指している必要のあるAddress
を指すので、バックポインタにはEclipseLinkの拡張機能@XmlInverseReference
が使用されています。
1対1および埋込みマッピングを作成するには:
1対多のリレーションシップのマッピング
JPAの@OneToMany
注釈は、ソース・エンティティの1つのインスタンスが同じターゲット・エンティティの複数のエンティティを参照できることを示しています。たとえば、1人の従業員に、固定電話、携帯電話、連絡先の電話、勤務先の予備の電話番号など、複数の電話番号がある場合があります。異なる各番号がPhoneNumber
エンティティのインスタンスになり、1つのEmployee
エンティティが各インスタンスを指す場合があります。
このタスクでは、従業員をその従業員の電話番号の1つにマッピングして、逆方向にもマッピングします。Employee
とPhoneNumber
のリレーションシップは双方向なので、この例でも、EclipseLinkの拡張機能@XmlInverseReference
を使用してバックポインタをマップします。
1対多マッピングを作成するには、次のようにします。
-
「タスク1: アクセッサ・タイプの定義およびクラスのインポート」の説明に従って、アクセッサ・タイプ
FIELD
がパッケージ・レベルで定義されていることを確認します。 -
@OneToMany
注釈をEmployee
エンティティに挿入することにより、リレーションシップの1方向(この場合は、PhoneNumber
のemployeeプロパティ)をマップします。@OneToMany(mappedBy="contact") private List<PhoneNumber> contactNumber;
mappedBy
フィールドは、このリレーションシップがcontact
フィールドによって所有されていることを示します。 -
@ManyToOne
および@XmlInverseMapping
注釈をPhoneNumber
エンティティに挿入することにより、戻り方向(この場合は、Employee
のphone numberプロパティ)をマップします。@ManyToOne @JoinColumn(name="E_ID", referencedColumnName = "E_ID") @XmlInverseReference(mappedBy="contactNumber") private Employee contact;
mappedBy
フィールドは、このリレーションシップがcontactNumber
フィールドによって所有されていることを示します。@JoinColumn
は、外部キーが格納される列(name="E_ID"
)および外部キーによって参照される列(referencedColumnName = "E_ID"
)を示しています。
タスク3: 共有参照リレーションシップのマッピング
共有参照リレーションシップは、ターゲット・オブジェクトが複数のソース・オブジェクトによって参照されている場合に発生します。たとえば、IT、人事、財務などの複数の部門にビジネスが区分されている場合があります。これらの各部門には、職責、賃金水準、勤務先などの異なる複数の従業員がいます。部門および従業員を管理するには、共有参照リレーションシップが必要になります。
XMLのネストを使用して共有参照リレーションシップを安全に表現することはできないので、キー・リレーションシップを使用します。JPAエンティティのIDフィールドを利用するには、EclipseLinkのJAXB @XmlID
注釈を文字列以外のフィールドとプロパティに、@XmlIDREF
を文字列のフィールドとプロパティに使用する必要があります。
この項では、多対1の共有参照リレーションシップおよび多対多の共有参照リレーションシップのマップ方法を示す例を示します。
JPAエンティティ
マッピングが作成されると、エンティティは次の例のようになります。
注意:
スペースを節約するために、パッケージ名、import文、およびget/setメソッドは、コード・サンプルから除かれています。すべての例で、標準のJPA注釈が使用されています。
例15-1 Employeeエンティティ
@Entity
public class Employee {
@Id
@Column(name="E_ID")
private BigDecimal eId;
private String name;
@OneToOne(mappedBy="resident")
private Address residence;
@OneToMany(mappedBy="contact")
private List<PhoneNumber> contactNumber;
@ManyToMany(mappedBy="member")
private List<Department> team;
}
例15-2 Addressエンティティ
@Entity
public class Address {
@Id
@Column(name="E_ID", insertable=false, updatable=false)
private BigDecimal eId;
private String city;
private String street;
@OneToOne
@JoinColumn(name="E_ID")
private Employee resident;
}
例15-3 PhoneNumberエンティティ
@Entity
@Table(name="PHONE_NUMBER")
public class PhoneNumber {
@Id
@Column(name="P_ID")
private BigDecimal pId;
@ManyToOne
@JoinColumn(name="E_ID", referencedColumnName = "E_ID")
private Employee contact;
private String num;
}
例15-4 Departmentエンティティ
@Entity
public class Department {
@Id
@Column(name="D_ID")
private BigDecimal dId;
private String name;
@ManyToMany
@JoinTable(name="DEPT_EMP", joinColumns =
@JoinColumn(name="D_ID", referencedColumnName = "D_ID"),
inverseJoinColumns = @JoinColumn(name="E_ID",
referencedColumnName = "E_ID"))
private List<Employee> member;
}
XMLへの複合主キーのバインディング
JPAエンティティに複合主キーがある場合、次の例のように、JAXB注釈およびEclipseLinkの特定の拡張機能を使用してバインドできます。
タスク2: ターゲット・オブジェクトの作成
ターゲット・オブジェクトを作成するには、次を実行します:
例15-5 複合主キーが設定されたEmployeeエンティティ
@Entity @IdClass(EmployeeId.class) public class Employee { @Id @Column(name="E_ID") @XmlID private BigDecimal eId; @Id @XmlKey private String country; @OneToMany(mappedBy="contact") @XmlInverseReference(mappedBy="contact") private List<PhoneNumber> contactNumber; } public class EmployeeId { public BigDecimal eId; public String country; public EmployeeId(BigDecimal eId, String country) { this.id = id; this.country = country;; } public boolean equals(Object other) { if (other instanceof EmployeeId) { final EmployeeId otherEmployeeId = (EmployeeId) other; return (otherEmployeeId.eId.equals(eId) && otherEmployeeId.country.equals(country)); } return false; } }
Employeeエンティティは例15-5のようになります。
タスク3: ソース・オブジェクトの作成
このタスクでは、ソース・オブジェクトPhoneNumber
エンティティを作成します。ターゲット・オブジェクトに複合キーがあるので、EclipseLinkの@XmlJoinNodes
注釈を使用してマッピングを設定する必要があります。
ソース・オブジェクトを作成するには:
例15-6 PhoneNumberエンティティ
@Entity public class PhoneNumber { @ManyToOne @JoinColumns({ @JoinColumn(name="E_ID", referencedColumnName = "E_ID"), @JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY") }) @XmlJoinNodes( { @XmlJoinNode(xmlPath="contact/id/text()", referencedXmlPath="id/text()"), @XmlJoinNode(xmlPath="contact/country/text()", referencedXmlPath="country/text()") }) private Employee contact; }
ターゲット・オブジェクトは例15-6のようになります。
XMLへの埋込みIDクラスのバインディング
埋込みIDでは、エンティティの主キーを格納する別個のEmbeddable
Javaクラスを定義します。これは@EmbeddedId
注釈を使用して定義します。埋込みIDのEmbeddable
クラスには、基本マッピングを使用してエンティティの各ID属性を定義する必要があります。埋込みIDのEmbeddable
のすべての属性は、主キーの一部であると見なされます。この演習では、JPAエンティティに埋込みIDクラスがある場合に、JAXBを使用して一連のJPAエンティティからXML表現を導出する方法を示します。
タスク2: ターゲット・オブジェクトの作成
ターゲット・オブジェクトはEmployee
というエンティティで、従業員の連絡先電話番号のマッピングが格納されています。このターゲット・オブジェクトを作成するには、DescriptorCustomizer
インタフェースを実装する必要があるので、EclipseLinkの@XmlCustomizer
注釈を含める必要があります。また、リレーションシップが双方向なので、@XmlInverseReference
注釈も実装する必要があります。
ターゲット・オブジェクトを作成するには:
例15-7 ターゲット・オブジェクトとしてのEmployeeエンティティ
@Entity @IdClass(EmployeeId.class) @XmlCustomizer(EmployeeCustomizer.class) public class Employee { @EmbeddedId private EmployeeId id; @OneToMany(mappedBy="contact") @XmlInverseReference(mappedBy="contact") private List<PhoneNumber> contactNumber; }
完成したターゲット・オブジェクトは例15-7のようになります。
タスク3: ソース・オブジェクトの作成
この例のソース・オブジェクトには複合キーがあるので、キーが自分自身にマップされるのを防ぐために、フィールド@XmlTransient
をマーク付けする必要があります。EclipseLinkの@XmlCustomizer
注釈を使用してマッピングを設定します。
ソース・オブジェクトを作成するには、次を実行します:
例15-8 ソース・オブジェクトとしてのPhoneNumberクラス
@Entity public class PhoneNumber { @ManyToOne @JoinColumns({ @JoinColumn(name="E_ID", referencedColumnName = "E_ID"), @JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY") }) @XmlJoinNodes( { @XmlJoinNode(xmlPath="contact/id/text()", referencedXmlPath="id/text()"), @XmlJoinNode(xmlPath="contact/country/text()", referencedXmlPath="country/text()") }) private Employee contact; }
@XmlJoinNodes( { @XmlJoinNode(xmlPath="contact/id/text()", referencedXmlPath="id/text()"), @XmlJoinNode(xmlPath="contact/country/text()", referencedXmlPath="country/text()") }) private Employee contact;
完成したPhoneNumber
クラスは例15-8のようになります。
タスク5: PhoneNumberCustomizerクラスとしてのDescriptorCustomizerの実装
タスク4で追加したコードは、新しい値に対してXMLObjectReferenceMappingsを作成する必要があることを示していました。これには、DescriptorCustomizer
をPhoneNumberCustomizer
として実装して、複数のキー・マッピングを追加する必要があります。この手順は、次のとおりです。
例15-9 更新されたキー・マッピングが指定されたPhoneNumber Customizer
import org.eclipse.persistence.config.DescriptorCustomizer; import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.oxm.mappings.XMLObjectReferenceMapping; public class PhoneNumberCustomizer implements DescriptorCustomizer { public void customize(ClassDescriptor descriptor) throws Exception { XMLObjectReferenceMapping contactMapping = new XMLObjectReferenceMapping(); contactMapping.setAttributeName("contact"); contactMapping.setReferenceClass(Employee.class); contactMapping.addSourceToTargetKeyFieldAssociation("contact/@eID", "eId/text()"); contactMapping.addSourceToTargetKeyFieldAssociation("contact/@country", "country/text()"); descriptor.addMapping(contactMapping); } }
PhoneNumberCustomizer
は例15-9のようになります。
EclipseLinkのXMLバインディング文書の使用
前述の例で示したように、EclipseLinkには、JPAエンティティをXML表現にマップする標準のJAXB注釈が実装されています。EclipseLinkのXMLバインディング文書を使用して、メタデータを表現することもできます。XMLバインディングを使用してマッピング情報を実際のJavaクラスから分離できるだけでなく、次のようなさらに高度なメタデータ・タスクに使用することもできます。
-
追加のマッピング情報による、既存の注釈の増強またはオーバーライド。
-
Java注釈を使用しない、すべてのマッピング情報の外部指定。
-
複数のバインディング文書間のマッピングの定義。
-
具体的なJavaフィールドに対応しない「仮想」マッピングの指定。
XMLバインディング文書の使用の詳細は、http://wiki.eclipse.org/EclipseLink/UserGuide/MOXy/Runtime/XML_Bindings
にあるJAXB/MOXyのドキュメントのXMLバインディングに関する項を参照してください。
XMLテキスト・ノードへの単純なJava値のマッピング
この項では、単純なJava値をXMLテキスト・ノードに直接マップする方法をいくつか示します。次の例を示します。
属性に対する値のマッピング
この例では、JavaオブジェクトCustomer
のid
プロパティを、<customer>
要素の属性として、そのXML表現にマップします。このXMLは例15-10のスキーマに基づいています。
例15-10 XMLスキーマの例
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:element name="customer" type="customer-type"/> <xsd:complexType name="customer-type"> <xsd:attribute name="id" type="xsd:integer"/> </xsd:complexType> </xsd:schema>
次の手順では、Javaオブジェクトのid
プロパティをマップする方法、つまり、EclipseLinkのObject-to-XML Mapping (OXM)メタデータ形式で値を表す方法を説明します。
Javaオブジェクトからのマッピング
Javaオブジェクトからこのマッピングを作成するために重要なのは、フィールドをXML属性にマップする@XmlAttribute
JAXB注釈です。このマッピングを作成するには:
例15-11 マップされたidプロパティが設定されたCustomerオブジェクト
package example; import javax.xml.bind.annotation.*; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlAttribute private Integer id; ... }
このオブジェクトは例15-11のようになります。
OXMメタデータ形式でのマッピングの定義
EclipseLinkのOXMメタデータ形式でマッピングを表す必要がある場合は、例15-12のとおり、eclipselink-oxm.xml
ファイルに定義されているXMLタグを使用して、適切な値を設定する必要があります。
例15-12 OXMメタデータ形式の属性としてのidのマッピング
... <java-type name="Customer"> <xml-root-element name="customer"/> <java-attributes> <xml-attribute java-attribute="id"/> </java-attributes> </java-type> ...
OXMメタデータ形式の詳細は、「XMLメタデータ表現を使用したJAXB注釈のオーバーライド」を参照してください。
テキスト・ノードへの値のマッピング
EclipseLinkでは、たとえば、単純なテキスト・ノード、単純な順序のテキスト・ノード、サブセットまたは位置別など、様々な種類のXMLテキスト・ノードにJavaオブジェクトから値を簡単にマップできます。これらのマッピングを次の例に示します。
単純なテキスト・ノードに対する値のマッピング
JavaオブジェクトのJAXB注釈を使用して、またはEclipseLinkのOXMメタデータ形式でマッピングを表して、Javaオブジェクトから値をマップできます。
JAXB注釈を使用したマッピング
文字列値を受け入れる<phone-number>
という要素が、関連するスキーマに定義されている場合は、@XmlValue
注釈を使用して、<phone-number>
ノードに文字列をマップできます。次を実行します。
例15-13 マップされたnumberプロパティが指定されたPhoneNumberオブジェクト
package example; import javax.xml.bind.annotation.*; @XmlRootElement(name="phone-number") @XmlAccessorType(XmlAccessType.FIELD) public class PhoneNumber { @XmlValue private String number; ... }
このオブジェクトは例15-13のようになります。
OXMメタデータ形式でのマッピングの定義
EclipseLinkのOXMメタデータ形式でマッピングを表す必要がある場合は、例15-14のとおり、eclipselink-oxm.xml
ファイルに定義されているXMLタグを使用して、適切な値を設定する必要があります。
例15-14 OXMメタデータ形式の属性としてのnumberのマッピング
... <java-type name="PhoneNumber"> <xml-root-element name="phone-number"/> <java-attributes> <xml-value java-attribute="number"/> </java-attributes> </java-type> ...
単純な順序でのテキスト・ノードへの値のマッピング
JAXB注釈を使用して、またはEclipseLinkのOXMメタデータ形式でマッピングを表して、顧客の名と姓のような値の順序を個別の要素としてマップできます。次の手順では、顧客の名と姓の値をマップする方法を示しています。
JAXB注釈を使用したマッピング
関連するスキーマに次の要素が定義されていると仮定します。
-
それ自身が
complexType
として定義されている、タイプcustomer-typeの<customer>
。 -
両方ともタイプ
string
の<first-name>
および<last-name>
という名前の順序要素。
@XmlElement
注釈を使用すれば、顧客の名と姓の値を適切なXMLノードにマップできます。これを行うには:
例15-15 単純な順序に対するCustomerオブジェクトの値のマッピング
package example; import javax.xml.bind.annotation.*; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlElement(name="first-name") private String firstName; @XmlElement(name="last-name") private String lastName; ... }
このオブジェクトは例15-15のようになります。
OXMメタデータ形式でのマッピングの定義
EclipseLinkのOXMメタデータ形式でマッピングを表す必要がある場合は、例15-16のとおり、eclipselink-oxm.xml
ファイルに定義されているXMLタグを使用して、適切な値を設定する必要があります。
例15-16 OXMメタデータ形式での順序属性のマッピング
... <java-type name="Customer"> <xml-root-element name="customer"/> <java-attributes> <xml-element java-attribute="firstName" name="first-name"/> <xml-element java-attribute="lastName" name="last-name"/> </java-attributes> </java-type> ...
サブ要素のテキスト・ノードに対する値のマッピング
JAXB注釈を使用して、またはEclipseLinkのOXMメタデータ形式でマッピングを表して、XML文書のサブ要素としてネストされているテキスト・ノードに、Javaオブジェクトから値をマップできます。たとえば、<customer>
ルートの<personal-info>
のサブ要素である<first-name>
および<last-name>
要素に値を設定する必要がある場合は、次の手順でマッピングを設定できます。
JAXB注釈を使用したマッピング
関連するスキーマに次の要素が定義されていると仮定します。
-
それ自身がcomplexTypeとして定義されている、タイプcustomer-typeの
<customer>
-
<personal-info>
-
両方ともタイプstringの
<first-name>
と<last-name>
という名前の<personal-info>
のサブ要素
JAXBの注釈を使用すれば、顧客の名と姓の値を適切なXMLサブ要素ノードにマップできます。この例では、単純な要素名のカスタマイズではなく、実際に新しいXML構造が導入されているので、EclipseLinkの@XmlPath
注釈を使用しています。このマッピングを実現するには:
例15-17 プロパティをサブ要素にマッピングするCustomerオブジェクト
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.*; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlPath("personal-info/first-name/text()") private String firstName; @XmlPath("personal-info/last-name/text()") private String lastName; ... }
このオブジェクトは例15-17のようになります。
OXMメタデータ形式でのマッピングの定義
EclipseLinkのOXMメタデータ形式でマッピングを表す必要がある場合は、例15-18のとおり、eclipselink-oxm.xml
ファイルに定義されているXMLタグを使用して、適切な値を設定する必要があります。
例15-18 OXMメタデータ形式のサブ要素としての属性のマッピング
... <java-type name="Customer"> <xml-root-element name="customer"/> <java-attributes> <xml-element java-attribute="firstName" xml-path="personal-info/first-name/text()"/> <xml-element java-attribute="lastName" xml-path="personal-info/last-name/text()"/> </java-attributes> </java-type> ...
位置によるテキスト・ノードへの値のマッピング
複数のノードの名前が同じ場合には、XML文書内の位置を指定して、Javaオブジェクトから値をマップします。これを行うには、属性の名前ではなく、属性の位置に値をマップします。これは、JAXB注釈を使用するか、EclipseLinkのOXMメタデータ形式でマッピングを表すことによって行えます。次の例では、2つの<name>
要素がXMLに含まれています。最初の名前は顧客の名を表し、2番名の名前は顧客の姓を表しています。
JAXB注釈を使用したマッピング
次の属性が定義されたXMLスキーマがあるとします。
-
それ自身が
complexType
として定義されている、タイプcustomer-typeの<customer>
-
タイプ
String
の<name>
この例でも、JAXBの@XmlPath
注釈を使用して、顧客の名と姓を適切な<name>
要素にマップします。また、@XmlType(propOrder)
注釈も使用して、要素が常に正しい位置に配置されるようにします。このマッピングを実現するには:
例15-19 位置により値をマップするCustomerオブジェクト
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath; @XmlRootElement @XmlType(propOrder={"firstName", "lastName"}) @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlPath("name[1]/text()") private String firstName; @XmlPath("name[2]/text()") private String lastName; ... }
このオブジェクトは例15-19のようになります。
XPath述語の使用方法の詳細は、「マッピング用のXPath述語の使用」を参照してください。
XMLメタデータ表現を使用したJAXB注釈のオーバーライド
EclipseLinkには、Java注釈の使用に加えて、JAXB注釈のかわりに使用したり、ソースのJAXB注釈をメタデータのXML表現で置換したりできるeclipselink-oxm.xml
という名前のXMLマッピング構成ファイルが用意されています。すべての標準JAXBマッピング機能を使用できるだけでなく、高度なマッピング・タイプとオプションも使用できます。
XMLのメタデータ表現は、次の場合に便利です。
-
サード・パーティから入手したなどの理由でドメイン・モデルを変更できない場合。
-
JAXBのAPIにコンパイル時の依存関係が存在しないようにする必要がある場合(Java SE 6より前のバージョンのJavaを使用している場合)。
-
複数のJAXBマッピングをドメイン・モデルに適用する必要がある場合(注釈では1つに制限されます)。
-
他のテクノロジからの多数の注釈がすでにオブジェクト・モデルに含まれているので、さらに追加するとクラスが読みにくくなる場合。
この項では、eclipselink-oxm.xml
を使用してJAXBの注釈をオーバーライドする方法を示します。
注意:
このマッピング・ファイルを使用すると多くの高度な機能が有効になりますが、他のJAXBの実装に移植できなくなる場合があります。
タスク1: XMLでの高度なマッピングの定義
まず、XMLマッピング・ファイルを更新して、eclipselink_oxm_2_3.xsd
スキーマを公開します。例15-20は、マッピング・ファイルの<xml-bindings>
要素を変更し、正しいネームスペースを指して、スキーマを利用する方法を示しています。各Javaパッケージには1つのマッピング・ファイルを指定できます。
例15-20 マッピング・ファイル内のXMLバインディング情報の更新
<?xml version="1.0"?> <xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eclipse.org/eclipselink/xsds/persistence/oxm http://www.eclipse.org/eclipselink/xsds/eclipselink_oxm_2_4.xsd" version="2.4"> </xml-bindings>
マッピングに対するXPath述語の使用
この項では、EclipseLinkのMOXy APIでXPath述語を使用して、XML要素の名前を指定する式を定義する方法を説明します。XPath述語とは、オブジェクトからXMLへの特定のマッピングを定義する式のことです。前述の例で示したように、JAXBのデフォルトでは、Javaのフィールド名がXML要素名として使用されます。
この項には次のサブセクションがあります。
XPath述語の理解
前述のとおり、XPath述語とは、標準の注釈時にオブジェクトからXMLへの特定のマッピングを定義する式のことです。
十分ではありません。たとえば、XMLの次のスニペットは、2つの<node>
サブ要素が指定された<data>
要素を示しています。このマッピングをJavaオブジェクトに作成する場合は、各<node>
サブ要素にXPath述語を指定する必要があります。たとえば、次のJavaでは、Node[2]
と指定されています。
<java-attributes>
<xml-element java-attribute="node" xml-path="node[1]/ABC"/>
<xml-element java-attribute="node" xml-path="node[2]/DEF"/>
</java-attributes>
これは、次のXMLで2番目に出現するnode要素("DEF"
)と一致します。
<?xml version="1.0" encoding="UTF-8"?>
<data>
<node>ABC</node>
<node>DEF</node>
</data>
このように、XPath述語を使用すれば、異なる属性値に対して同じ属性名を使用できます。
属性値に基づくマッピング
EclipseLink MOXy 2.3以降では、属性値に基づいてXML要素にマップすることもできます。この演習では、JPAエンティティに注釈を付けて、例15-21に示されているXML文書をレンダリングします。すべてのXML要素は名前付きノードですが、名前属性の値によって区別されていることに注意してください。
例15-21
<?xml version="1.0" encoding="UTF-8"?> <node> <node name="first-name">Bob</node> <node name="last-name">Smith</node> <node name="address"> <node name="street">123 A Street</node> </node> <node name="phone-number" type="work">555-1111</node> <node name="phone-number" type="cell">555-2222</node> </node>
このマッピングを実現するには、Name
、Address
およびPhoneNumber
の3つのクラスを宣言し、element-name
[@
attribute-name
='
value
']
の形式でXPathを使用して、各Javaフィールドをマップする必要があります。
タスク1: Customerエンティティの作成
Customer
クラス・エンティティを作成するには:
例15-22 属性値にマッピングされているCustomerオブジェクト
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath; @XmlRootElement(name="node") @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlPath("node[@name='first-name']/text()") private String firstName; @XmlPath("node[@name='last-name']/text()") private String lastName; @XmlPath("node[@name='address']") private Address address; @XmlPath("node[@name='phone-number']") private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>(); ... }
Customer
クラスは例15-22のスニペットのようになります。
タスク2: Addressエンティティの作成
Address
クラスを作成するには、次を実行します:
例15-23 属性値に対するAddressオブジェクトのマッピング
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath; @XmlAccessorType(XmlAccessType.FIELD) public class Address { @XmlPath("node[@name='street']/text()") private String street; ... }
Address
クラスは例15-23のようになります。
タスク3: PhoneNumberエンティティの作成
PhoneNumber
エンティティを作成するには:
例15-24 属性値に対するPhoneNumberオブジェクトのマッピング
package example; import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) public class PhoneNumber { @XmlAttribute private String type; @XmlValue private String number; ... }
PhoneNumber
オブジェクトは例15-24のようになります。
セルフ・マッピング
セルフ・マッピングは、ターゲット・オブジェクトからのデータがソース・オブジェクトのXML要素内に表示されるようにターゲット・オブジェクトのXPathを"."(ドット)に設定した場合に、1対1のマッピングで発生します。この演習では、「属性値に基づくマッピング」の例を使用して、Addressの情報が、自分の要素にラップされずにcustomer要素に直接表示されるようにマップします。
セルフ・マッピングを作成するには:
例15-25 セルフ・マッピングされたAddress要素が指定されたXMLノード
<?xml version="1.0" encoding="UTF-8"?> <node> <node name="first-name">Bob</node> <node name="last-name">Smith</node> <node name="street">123 A Street</node> <node name="phone-number" type="work">555-1111</node> <node name="phone-number" type="cell">555-2222</node> </node>
Customerエンティティに対してレンダリングされるXMLは例15-25のようになります。
動的JAXB/MOXyの使用
動的JAXB/MOXyでは、様々なメタデータ・ソースからJAXBContext
をブートストラップでき、コンパイルされたドメイン・クラスがなくても、使い慣れたJAXBのAPIを使用してデータのマーシャルおよびアンマーシャルを行えます。以前に生成されたJavaソース・コードを更新および再コンパイルしなくてもメタデータを更新できるようになったので、これは静的JAXBに対する拡張機能になります。
動的JAXB/MOXyのエンティティを使用すると、次のような利点があります。
-
実際のJavaクラス(たとえば、
Customer.class
、Address.class
)を使用するかわりに、ドメイン・オブジェクトがDynamicEntity
のサブクラスになります。 -
動的エンティティには、データを操作するための
get(propertyName)
/set(propertyName propertyValue)
という簡単なAPIが用意されています。 -
動的エンティティには、メタデータの解析時にメモリー内に生成される
DynamicType
が関連付けられています。
次のタスクで、動的JAXBの使用方法を示します。
タスク1: XMLスキーマからの動的JAXBContextのブートストラップ
この例は、XMLスキーマから動的JAXBContext
をブートストラップする方法を示しています。
XMLスキーマからのブートストラップ
DynamicJAXBContextFactory
を使用して、動的JAXBContext
を作成します。例15-26は、createContextFromXSD()
を使用してDynamicJAXBContext
をcustomer.xsd
スキーマ(例15-27)からブートストラップしています。
例15-26 入力ストリームの指定およびDynamicJAXBContextの作成
import java.io.FileInputStream; import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext; import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContextFactory; public class Demo { public static void main(String[] args) throws Exception { FileInputStream xsdInputStream = new FileInputStream("src/example/customer.xsd"); DynamicJAXBContext jaxbContext = DynamicJAXBContextFactory.createContextFromXSD(xsdInputStream, null, null, null);
最初のパラメータはXMLスキーマ自身を表し、java.io.InputStream
、org.w3c.dom.Node
、javax.xml.transform.Source
のいずれかの形式である必要があります。
XMLスキーマ
例15-27は、ブートストラップする対象の動的JAXBContextのメタデータを表すcustomer.xsd
スキーマを示しています。
例15-27 サンプルXMLスキーマ文書
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.example.org" targetNamespace="http://www.example.org" elementFormDefault="qualified"> <xsd:complexType name="address"> <xsd:sequence> <xsd:element name="street" type="xsd:string" minOccurs="0"/> <xsd:element name="city" type="xsd:string" minOccurs="0"/> </xsd:sequence> </xsd:complexType> <xsd:element name="customer"> <xsd:complexType> <xsd:sequence> <xsd:element name="name" type="xsd:string" minOccurs="0"/> <xsd:element name="address" type="address" minOccurs="0"/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema>
スキーマのインポート/インクルードの処理
他のスキーマのインポートが格納されたXMLスキーマからDynamicJAXBContext
をブートストラップするには、org.xml.sax.EntityResolver
を構成し、インポートされたスキーマの場所を解決して、EntityResolver
をDynamicJAXBContextFactory
に渡す必要があります。
次の例は、customer.xsd
(例15-28)とaddress.xsd
(例15-29)の2つのスキーマ文書を示しています。次の文を使用して、customer.xsd
がaddress.xsd
をインポートしていることがわかります。
<xsd:import namespace="http://www.example.org/address" schemaLocation="address.xsd"/>
例15-28 customer.xsd
<?xml version="1.0" encoding="UTF-8"?>
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:add="http://www.example.org/address"
xmlns="http://www.example.org/customer"
targetNamespace="http://www.example.org/customer"
elementFormDefault="qualified">
<xsd:import namespace="http://www.example.org/address" schemaLocation="address.xsd"/>
<xsd:element name="customer">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="name" type="xsd:string" minOccurs="0"/>
<xsd:element name="address" type="add:address" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
例15-29 address.xsd
<?xml version="1.0" encoding="UTF-8"?> xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.example.org/address" targetNamespace="http://www.example.org/address" elementFormDefault="qualified"> <xsd:complexType name="address"> <xs:sequence> <xs:element name="street" type="xs:string"/> <xs:element name="city" type="xs:string"/> </xs:sequence> </xsd:complexType> </xsd:schema>
EntityResolverを実装して渡す方法
DynamicJAXBContext
をcustomer.xsd
スキーマからブートストラップする場合は、エンティティ・リゾルバを渡す必要があります。次を実行します。
- インポートされたスキーマの場所を解決するには、例15-30に示されたコードを指定して、
entityResolver
を実装する必要があります。 DynamicJAXBContext
を実装した後、例15-31に示すようにEntityResolver
を渡します。
例15-30 EntityResolverの実装
class MyEntityResolver implements EntityResolver { public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { // Imported schemas are located in ext\appdata\xsd\ // Grab only the filename part from the full path String filename = new File(systemId).getName(); // Now prepend the correct path String correctedId = "ext/appdata/xsd/" + filename; InputSource is = new InputSource(ClassLoader.getSystemResourceAsStream(correctedId)); is.setSystemId(correctedId); return is; } }
例15-31 EntityResolverを渡す方法
FileInputStream xsdInputStream = new FileInputStream("src/example/customer.xsd");
DynamicJAXBContext jaxbContext =
DynamicJAXBContextFactory.createContextFromXSD(xsdInputStream, new MyEntityResolver(), null, null);
エラー処理
別のスキーマをインポートする際に、次の例外が表示される場合があります。
Internal Exception: org.xml.sax.SAXParseException: schema_reference.4: Failed to read schemadocument '<imported-schema-name>', because 1) could not find the document; 2) the document couldnot be read; 3) the root element of the document is not <xsd:schema>.
この例外を回避するには、noCorrectnessCheck
Javaプロパティを設定して、XJCのスキーマ正当性チェックを無効にします。このプロパティは、次の2つのいずれかの方法で設定できます。
-
コード内に次の行を追加します。
System.setProperty("com.sun.tools.xjc.api.impl.s2j.SchemaCompilerImpl.noCorrectnessCheck", "true")
-
コマンドラインから次のコマンドを使用します。
-Dcom.sun.tools.xjc.api.impl.s2j.SchemaCompilerImpl.noCorrectnessCheck=true
タスク2: 動的エンティティの作成とXMLへのマーシャル
この例は、動的エンティティを作成してXMLにマーシャルする方法を示しています。
動的エンティティの作成
DynamicJAXBContext
を使用して、DynamicEntity
のインスタンスを作成します。エンティティとプロパティ名は、静的JAXBを使用していれば生成されたであろうクラスとプロパティ名(この場合は、customer
とaddress
)に対応しています。
例15-32 動的エンティティの作成
DynamicEntity customer = jaxbContext.newDynamicEntity("org.example.Customer"); customer.set("name", "Jane Doe"); DynamicEntity address = jaxbContext.newDynamicEntity("org.example.Address"); address.set("street", "1 Any Street").set("city", "Any Town"); customer.set("address", address);
XMLへの動的エンティティのマーシャリング
DynamicJAXBContext
から入手したマーシャラは、標準のマーシャラで、DynamicEntityのインスタンスをマーシャルするために通常どおり使用できます。
例15-33 標準の動的JAXBマーシャラ
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);marshaller.marshal(customer, System.out);
例15-34に結果のXML文書を示します。
例15-34 <address>要素とその属性が表示された更新済のXML文書
<?xml version="1.0" encoding="UTF-8"?> <customer xmlns="www.example.org"> <name>Jane Doe</name> <address> <street>1 Any Street</street> <city>Any Town</city> </address> </customer>
タスク3: XMLからの動的エンティティのアンマーシャル
この例では、「タスク2: 動的エンティティの作成とXMLへのマーシャル」で作成した動的エンティティをXMLからアンマーシャルする方法を示します。参照されているXMLは例15-34に示されています。
XMLからの動的エンティティのアンマーシャル
DynamicJAXBContext
から入手したアンマーシャラは、標準のアンマーシャラで、DynamicEntity
のインスタンスをアンマーシャルするために通常どおり使用できます。
例15-35 標準の動的JAXBアンマーシャラ
FileInputStream xmlInputStream = new FileInputStream("src/example/dynamic/customer.xml");
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
DynamicEntity customer = (DynamicEntity) unmarshaller.unmarshal(xmlInputStream);
動的エンティティからのデータの取得
次に、動的エンティティのどのデータを取得するかを指定します。System.out.println()
を使用して、この値を指定し、エンティティ名で渡します。DynamicEntity
には、たとえば、getName()
のかわりにget("name")
のようなプロパティに基づいたデータ・アクセスが用意されています。
System.out.println(customer.<String>get("name"));
DynamicTypeを使用した、動的エンティティのイントロスペクション
DynamicEntity
のインスタンスには、例15-36に示されているようにDynamicEntity
をイントロスペクトするために使用できる、対応するDynamicType
があります。
例15-36
DynamicType addressType = jaxbContext.getDynamicType("org.example.Address");
DynamicEntity address = customer.<DynamicEntity>get("address");
for(String propertyName: addressType.getPropertiesNames()) {
System.out.println(address.get(propertyName));
}