この章では、POJO (Plain Old Java Object)をXMLにマッピングするためのJava EE標準であるJava Architecture for XML Binding (JAXB)でJPAを使用する方法、およびJPAエンティティをXMLにマッピングするMapping Objects to XML (MOXy)拡張機能を使用する方法について説明します。XMLへのJPAエンティティのマッピングは、Java API for RESTful Web Services (JAX-RS)、Java API for XML Web Services (JAX-WS)またはSpringを使用してデータ・アクセス・サービスを作成する場合に便利です。
この章には次のトピックが含まれます:
ユース・ケース
JPAエンティティをユーザーがXMLにマップする必要があります。
解決方法
TopLinkでは、EclipseLinkのJAXBの拡張を介して、JAXB標準をサポートしています。
コンポーネント
TopLink 12cリリース1 (12.1.2)以上。
注意: TopLinkのコア機能は、オープン・ソースのEclipse Foundationの永続性フレームワークであるEclipseLinkによって提供されています。EclipseLinkでは、Java Persistence API (JPA)、Java Architecture for XML Binding (JAXB)、および標準に基づいたその他の永続性テクノロジと、それらの標準の拡張が実装されます。TopLinkには、EclipseLinkのすべてに加え、Oracleの追加機能が含まれています。 |
XML文書
例
関連情報については、次のEclipseLinkおよびJAXBの例を参照してください。
この章では、XMLにJPAエンティティをマッピングする一般的な方法をいくつか示します。次の例を扱うには、JAXB、MOXy、XMLバインディング、JAXB注釈のオーバーライド方法などのJPAからXMLへのマッピングの全体的な概念を理解している必要があります。次の項では、これらの概念の基礎的なことがらを説明します。
XMLバインディングとは、コンピュータのメモリーのオブジェクトとして、XML文書内に情報を表現する方法です。これにより、Domain Object Model (DOM)、Simple API for XML (SAX)またはStreaming API for XML (StAX)を使用してXML自身の直接表現からデータを取得しなくても、オブジェクトからXML内のデータにアクセスできるようになります。バインディングする際に、JAXBでは、JPAエンティティのグラフにツリー構造が適用されます。グラフの複数ツリー表現は可能で、選択されたルート・オブジェクトおよびリレーションシップがトラバースする方向に依存します。
JAXBによるXMLバインディングの例は15.2項「XMLへのJPAエンティティのバインディング」にあります。
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は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を参照してください。
JPAからXMLへのマッピングに注釈を使用することは、常に最も効果的とはかぎりません。たとえば、次の場合にはJAXBを使用しません。
サード・パーティのクラスにメタデータを指定する必要があるが、ソースにアクセスできない場合。
複数のXMLスキーマにオブジェクト・モデルをマップする必要がある場合。JAXBのルールでは、注釈を使用して複数のマッピングを適用することはできません。
オブジェクト・モデルにすでに多くの注釈(たとえば、JPA、Spring、JSR-303などのサービスから)が含まれていて、他の場所でメタデータを指定する必要がある場合。
これらの状況および類似の状況では、eclipselink_oxm.xml
ファイルを公開して、XMLデータ表現を使用できます。
XMLメタデータは、次の2つのモードで動作します。
注釈で指定されたメタデータに追加します。これは、次の場合に便利です。
XML表現のバージョンのひとつが注釈に定義されており、XMLメタデータを使用して将来のバージョンのメタデータを微調整する場合。
標準のJAXB注釈を使用し、かつMOXy拡張機能用のXMLメタデータを使用する場合。このようにすると、コンパイル時の依存関係がオブジェクト・モデルに新たに生じることはありません。
注釈メタデータを完全に置換します。これは、様々なXML表現にマップする必要がある場合に便利です。
XMLデータ表現の使用方法の詳細は、15.4項「XMLメタデータ表現を使用したJAXB注釈のオーバーライド」を参照してください。
次の例は、JAXB注釈を使用してJPAエンティティをXMLにバインドする方法を示しています。バインディングの詳細は、15.1.1項「XMLバインディングの理解」を参照し、JAXBの詳細は、15.1.2項「JAXBの理解」を参照してください。
次の演習問題は、JAXBを使用して一連のJPAエンティティからXML表現を導出する方法を示しています。このプロセスは「バインディング」と呼ばれます(XMLバインディングの詳細は、15.2項「XMLへのJPAエンティティのバインディング」を参照してください)。これらの例は、次の2つの一般的なJPAリレーションシップをバインドする方法を示しています。
私有のリレーションシップ
共有参照のリレーションシップ
従業員エンティティを、その従業員の電話番号、住所および部門にマッピングしています。
次のすべての例で、同じアクセッサ・タイプFIELD
を使用しているので、JAXB注釈@XmlAccessorType
を使用してパッケージ・レベルでアクセッサ・タイプを定義します。この時点で、必要なクラスもインポートします。
@XmlAccessorType(XmlAccessType.FIELD) package com.example.model; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType;
「私有」のリレーションシップは、ターゲット・オブジェクトが1つのソース・オブジェクトのみによって参照されている場合に発生します。このタイプのリレーションシップは、1対1および埋込み、または1対多のどちらの場合にも可能です。
このタスクでは、Employee
エンティティと、Address
およびPhoneNumber
エンティティの間のこれら両方のタイプのリレーションシップに対して双方向のマッピングを作成する方法を示します。
JPAの@OneToOne
および@Embedded
注釈は、ソース・エンティティの1つのインスタンスのみが、同じターゲット・エンティティ・インスタンスを参照できることを示しています。この例は、Employee
エンティティをAddress
エンティティにマップする方法、およびその逆にマップする方法を示しています。従業員は1つの住所のみに関連付けられるので、1対1マッピングと見なされます。このリレーションシップは双方向、つまりEmployee
はEmployee
側からも指している必要のあるAddress
を指すので、バックポインタにはEclipseLinkの拡張機能@XmlInverseReference
が使用されています。
1対1および埋込みマッピングを作成するには:
15.2.1.1項「タスク1: アクセッサ・タイプの定義およびクラスのインポート」の説明に従って、アクセッサ・タイプFIELD
がパッケージ・レベルで定義されていることを確認します。
@OneToOne
注釈をEmployee
エンティティに挿入することにより、リレーションシップの1方向(この場合は、Address
のemployee
プロパティ)をマップします。
@OneToOne(mappedBy="resident") private Address residence;
mappedBy
引数は、リレーションシップがresident
フィールドによって所有されていることを示します。
@OneToOne
および@XmlInverseMapping
注釈をAddressエンティティに挿入することにより、戻り方向(この場合は、Employee
のaddress
プロパティ)をマップします。
@OneToOne @JoinColumn(name="E_ID") @XmlInverseReference(mappedBy="residence") private Employee resident;
mappedBy
フィールドは、このリレーションシップがresidence
フィールドによって所有されていることを示します。@JoinColumn
は、外部キーが格納される列を示します。
エンティティは、例15-1および例15-2に示すエンティティと同じように指定してください。
JPAの@OneToMany
注釈は、ソース・エンティティの1つのインスタンスが同じターゲット・エンティティの複数のエンティティを参照できることを示しています。たとえば、1人の従業員に、固定電話、携帯電話、連絡先の電話、勤務先の予備の電話番号など、複数の電話番号がある場合があります。異なる各番号がPhoneNumber
エンティティのインスタンスになり、1つのEmployee
エンティティが各インスタンスを指す場合があります。
このタスクでは、従業員をその従業員の電話番号の1つにマッピングして、逆方向にもマッピングします。Employee
とPhoneNumber
のリレーションシップは双方向なので、この例でも、EclipseLinkの拡張機能@XmlInverseReference
を使用してバックポインタをマップします。
1対多マッピングを作成するには、次のようにします。
15.2.1.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"
)を示しています。
共有参照リレーションシップは、ターゲット・オブジェクトが複数のソース・オブジェクトによって参照されている場合に発生します。たとえば、IT、人事、財務などの複数の部門にビジネスが区分されている場合があります。これらの各部門には、職責、賃金水準、勤務先などの異なる複数の従業員がいます。部門および従業員を管理するには、共有参照リレーションシップが必要になります。
XMLのネストを使用して共有参照リレーションシップを安全に表現することはできないので、キー・リレーションシップを使用します。JPAエンティティのIDフィールドを利用するには、EclipseLinkのJAXB @XmlID
注釈を文字列以外のフィールドとプロパティに、@XmlIDREF
を文字列のフィールドとプロパティに使用する必要があります。
この項では、多対1の共有参照リレーションシップおよび多対多の共有参照リレーションシップのマップ方法を示す例を示します。
多対1のマッピングでは、ソース・エンティティの1つ以上のインスタンスが同じターゲット・エンティティのインスタンスを参照できます。この例は、従業員の複数の電話番号のいずれか1つに従業員をマップする方法を示しています。
多対1の共有参照リレーションシップをマップするには:
15.2.1.1項「タスク1: アクセッサ・タイプの定義およびクラスのインポート」の説明に従って、アクセッサ・タイプFIELD
がパッケージ・レベルで定義されていることを確認します。
@ManyToOne
注釈をPhoneNumber
エンティティに挿入することにより、リレーションシップの1方向(この場合は、Employee
のphone numberプロパティ)をマップします。
@ManyToOne @JoinColumn(name="E_ID", referencedColumnName = "E_ID") @XmlIDREF private Employee contact;
@JoinColumn
は、外部キーが格納される列(name="E_ID"
)および外部キーによって参照される列(referencedColumnName = "E_ID"
)を示しています。@XmlIDREF
注釈は、これが、対応する表の主キーになることを示しています。
@OneToMany
および@XmlInverseMapping
注釈をAddressエンティティに挿入することにより、戻り方向(この場合は、PhoneNumberのemployeeプロパティ)をマップします。
@OneToMany(mappedBy="contact") @XmlInverseReference(mappedBy="contact") private List<PhoneNumber> contactNumber;
両方の注釈のmappedBy
フィールドは、このリレーションシップがcontact
フィールドによって所有されていることを示しています。
@ManyToMany
注釈は、ソース・エンティティの1つ以上のインスタンスが、ターゲット・エンティティの1つ以上のインスタンスを参照できることを示しています。Department
とEmployee
のリレーションシップは双方向なので、この例でも、EclipseLinkの@XmlInverseReference
注釈を使用してバックポインタを表現しています。
多対多の共有参照リレーションシップをマップするには、次を実行します:
15.2.1.1項「タスク1: アクセッサ・タイプの定義およびクラスのインポート」の説明に従って、アクセッサ・タイプFIELD
がパッケージ・レベルで定義されていることを確認します。
次のコードを挿入して、Department
エンティティを作成します。
@Entity public class Department {
次のコードを挿入して、多対多のリレーションシップおよびエンティティの結合表を、このエンティティに定義します。
@ManyToMany @JoinTable(name="DEPT_EMP", joinColumns = @JoinColumn(name="D_ID", referencedColumnName = "D_ID"), inverseJoinColumns = @JoinColumn(name="E_ID", referencedColumnName = "E_ID"))
このコードにより、DEPT_EMP
という結合表が作成され、外部キーが格納される列(name="E_ID"
)および外部キーによって参照される列(referencedColumnName = "E_ID"
)が指定されます。さらに、関連付けの逆側のプライマリ表も指定されます。
最初のマッピング(この場合は、Department
のemployee
プロパティ)を完了し、次のコードを挿入して、それをこのエンティティの外部キーにします。
@XmlIDREF private List<Employee> member;
15.2.1.2.1項「1対1および埋込みリレーションシップのマッピング」で作成したEmployee
エンティティに、次のコードを挿入して、eId
がJPA (@Id
注釈)およびJAXB (@XmlID
注釈)の主キーであることを指定します。
@Id @Column(name="E_ID") @XmlID private BigDecimal eId;
さらにEmployee
エンティティに、次のコードを挿入して、リターン・マッピングを指定します。
@ManyToMany(mappedBy="member") @XmlInverseReference(mappedBy="member") private List<Department> team;
マッピングが作成されると、エンティティは次の例のようになります。
注意: スペースを節約するために、パッケージ名、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;
}
JPAエンティティに複合主キーがある場合、次の例のように、JAXB注釈およびEclipseLinkの特定の拡張機能を使用してバインドできます。
15.2.1.1項「タスク1: アクセッサ・タイプの定義およびクラスのインポート」の説明に従って、アクセッサ・タイプにFIELD
を定義します。
ターゲット・オブジェクトを作成するには、次を実行します:
エンティティの複数のフィールドまたはプロパティにマップするためのEmployeeID
というコンポジット主キー・クラスを指定して、Employee
を作成します。
@Entity
@IdClass(EmployeeId.class)
public class Employee {
エンティティの最初の主キーeIdを指定して、列にマップします。
@Id @Column(name="E_ID") @XmlID private BigDecimal eId;
2番目の主キーcountryを指定します。この場合、@XmlID
の注釈を付けられるのは1つのプロパティ(eId
)のみなので、@XmlKey
を使用して主キーを指定する必要があります。
@Id @XmlKey private String country;
@XmlKey
注釈により、ソース・オブジェクトの@XmlJoinNode
注釈を介したキーに基づくマッピングを使用して参照されるキーとして、プロパティがマーク付けされます。スキーマ・タイプIDにプロパティをバインドする必要がない点以外は@XmlKey
注釈に似ています。これは、@XmlKey
注釈の一般的な適用方法です。
次のコードを挿入して、PhoneNumber
のEmployee
プロパティの1対多マッピングを作成します。
@OneToMany(mappedBy="contact") @XmlInverseReference(mappedBy="contact") private List<PhoneNumber> contactNumber;
Employeeエンティティは例15-5のようになります。
例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; } }
このタスクでは、ソース・オブジェクトPhoneNumber
エンティティを作成します。ターゲット・オブジェクトに複合キーがあるので、EclipseLinkの@XmlJoinNodes
注釈を使用してマッピングを設定する必要があります。
ソース・オブジェクトを作成するには:
PhoneNumber
エンティティを作成します。
@Entity public class PhoneNumber {
多対1のリレーションシップを作成して、結合列を定義します。
@ManyToOne @JoinColumns({ @JoinColumn(name="E_ID", referencedColumnName = "E_ID"), @JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY") })
EclipseLinkの@XmlJoinNodes
注釈を使用して、マッピングを設定します。
@XmlJoinNodes( { @XmlJoinNode(xmlPath="contact/id/text()", referencedXmlPath="id/text()"), @XmlJoinNode(xmlPath="contact/country/text()", referencedXmlPath="country/text()") })
contact
プロパティを定義します。
private Employee contact; }
ターゲット・オブジェクトは例15-6のようになります。
例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; }
埋込みIDでは、エンティティの主キーを格納する別個のEmbeddable
Javaクラスを定義します。これは@EmbeddedId
注釈を使用して定義します。埋込みIDのEmbeddable
クラスには、基本マッピングを使用してエンティティの各ID属性を定義する必要があります。埋込みIDのEmbeddable
のすべての属性は、主キーの一部であると見なされます。この演習では、JPAエンティティに埋込みIDクラスがある場合に、JAXBを使用して一連のJPAエンティティからXML表現を導出する方法を示します。
15.2.1.1項「タスク1: アクセッサ・タイプの定義およびクラスのインポート」の説明に従って、XMLアクセッサ・タイプにFIELD
を定義します。
ターゲット・オブジェクトはEmployee
というエンティティで、従業員の連絡先電話番号のマッピングが格納されています。このターゲット・オブジェクトを作成するには、DescriptorCustomizer
インタフェースを実装する必要があるので、EclipseLinkの@XmlCustomizer
注釈を含める必要があります。また、リレーションシップが双方向なので、@XmlInverseReference
注釈も実装する必要があります。
ターゲット・オブジェクトを作成するには:
Employee
エンティティを作成します。@IdClass
注釈を使用し、EmployeeID
クラスがエンティティの複数のプロパティにマップされるようにします。
@Entity
@IdClass(EmployeeId.class)
public class Employee {
}
id
プロパティを定義して、埋込み可能にします。
@EmbeddedId @XmlPath("."); private EmployeeId id;
1対多のマッピング(この場合は、PhoneNumber
のemployee
プロパティ)を定義します。リレーションシップが双方向なので、@XmlInverseReference
を使用してリターン・マッピングを定義します。mappedBy
引数で示されているように、これら両方のリレーションシップはcontactフィールドによって所有されます。
@OneToMany(mappedBy="contact") @XmlInverseReference(mappedBy="contact") private List<PhoneNumber> contactNumber;
完成したターゲット・オブジェクトは例15-7のようになります。
この例のソース・オブジェクトには複合キーがあるので、キーが自分自身にマップされるのを防ぐために、フィールド@XmlTransient
をマーク付けする必要があります。EclipseLinkの@XmlCustomizer
注釈を使用してマッピングを設定します。
ソース・オブジェクトを作成するには、次を実行します:
PhoneNumber
エンティティを作成します。
@Entity public class PhoneNumber { }
多対1のマッピングを作成して、結合列を定義します。
@ManyToOne @JoinColumns({ @JoinColumn(name="E_ID", referencedColumnName = "E_ID"), @JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY") })
EclipseLinkの注釈拡張である@XmlJoinNodes
を使用し、マッピングにXMLノードを定義します。ターゲット・オブジェクトのIDが1つである場合、@XmlIDREF
注釈を使用します。
@XmlJoinNodes( { @XmlJoinNode(xmlPath="contact/id/text()", referencedXmlPath="id/text()"), @XmlJoinNode(xmlPath="contact/country/text()", referencedXmlPath="country/text()") }) private Employee contact;
完成したPhoneNumber
クラスは例15-8のようになります。
例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; }
タスク4で追加したコードは、新しい値に対してXMLObjectReferenceMappingsを作成する必要があることを示していました。これには、DescriptorCustomizer
をPhoneNumberCustomizer
として実装して、複数のキー・マッピングを追加する必要があります。これを行うには:
DescriptorCustomizer
をPhoneNumberCustomizer
として実装します。org.eclipse.persistence.oxm.mappings.XMLObjectReferenceMapping
を必ずインポートしてください。
import org.eclipse.persistence.oxm.mappings.XMLObjectReferenceMapping;
public class PhoneNumberCustomizer implements DescriptorCustomizer {
customize
メソッドで、次のマッピングを更新します。
contactMapping.setAttributeName
を"contact"
にします。
contactMapping.addSourceToTargetKeyFieldAssociation
を"contact/@eID", "eId/text()"
にします。
contactMapping.addSourceToTargetKeyFieldAssociation
を"contact/@country", "country/text()"
にします。
PhoneNumberCustomizer
は例15-9のようになります。
例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); } }
前述の例で示したように、EclipseLinkには、JPAエンティティをXML表現にマップする標準のJAXB注釈が実装されています。EclipseLinkのXMLバインディング文書を使用して、メタデータを表現することもできます。XMLバインディングを使用してマッピング情報を実際のJavaクラスから分離できるだけでなく、次のようなさらに高度なメタデータ・タスクに使用することもできます。
追加のマッピング情報による、既存の注釈の増強またはオーバーライド。
Java注釈を使用しない、すべてのマッピング情報の外部指定。
複数のバインディング文書間のマッピングの定義。
具体的なJavaフィールドに対応しない「仮想」マッピングの指定。
XMLバインディング文書の使用の詳細は、http://wiki.eclipse.org/EclipseLink/UserGuide/MOXy/Runtime/XML_Bindings
にあるJAXB/MOXyのドキュメントのXMLバインディングに関する項を参照してください。
この項では、単純な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オブジェクトからこのマッピングを作成するために重要なのは、フィールドをXML属性にマップする@XmlAttribute
JAXB注釈です。このマッピングを作成するには:
オブジェクトを作成して、javax.xml.bind.annotation.*
をインポートします。
package example; import javax.xml.bind.annotation.*;
Customer
クラスを宣言し、@XmlRootElement
注釈を使用してルート要素にします。XMLアクセッサ・タイプにFIELD
を設定します。
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer {
Customer
クラスのid
プロパティを属性としてマップします。
@XmlAttribute
private Integer id;
このオブジェクトは例15-11のようになります。
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メタデータ形式の詳細は、15.4項「XMLメタデータ表現を使用したJAXB注釈のオーバーライド」を参照してください。
EclipseLinkでは、たとえば、単純なテキスト・ノード、単純な順序のテキスト・ノード、サブセットまたは位置別など、様々な種類のXMLテキスト・ノードにJavaオブジェクトから値を簡単にマップできます。これらのマッピングを次の例に示します。
JavaオブジェクトのJAXB注釈を使用して、またはEclipseLinkのOXMメタデータ形式でマッピングを表して、Javaオブジェクトから値をマップできます。
文字列値を受け入れる<phone-number>
という要素が、関連するスキーマに定義されている場合は、@XmlValue
注釈を使用して、<phone-number>
ノードに文字列をマップできます。次を実行します:
オブジェクトを作成して、javax.xml.bind.annotation.*
をインポートします。
package example; import javax.xml.bind.annotation.*;
PhoneNumber
クラスを宣言し、@XmlRootElement
注釈を使用して、そのクラスをphone-numberという名前のルート要素にします。XMLアクセッサ・タイプにFIELD
を設定します。
@XmlRootElement(name="phone-number") @XmlAccessorType(XmlAccessType.FIELD) public class PhoneNumber {
Customerクラスのnumber
プロパティの前の行に@XmlValue
注釈を挿入し、この値を属性としてマップします。
@XmlValue
private String number;
このオブジェクトは例15-13のようになります。
EclipseLinkのOXMメタデータ形式でマッピングを表す必要がある場合は、例15-14のとおり、eclipselink-oxm.xml
ファイルに定義されているXMLタグを使用して、適切な値を設定する必要があります。
JAXB注釈を使用して、またはEclipseLinkのOXMメタデータ形式でマッピングを表して、顧客の名と姓のような値の順序を個別の要素としてマップできます。次の手順では、顧客の名と姓の値をマップする方法を示しています。
関連するスキーマに次の要素が定義されていると仮定します。
それ自身がcomplexType
として定義されている、タイプcustomer-typeの<customer>
。
両方ともタイプstring
の<first-name>
および<last-name>
という名前の順序要素。
@XmlElement
注釈を使用すれば、顧客の名と姓の値を適切なXMLノードにマップできます。これを行うには:
オブジェクトを作成して、javax.xml.bind.annotation.*
をインポートします。
package example; import javax.xml.bind.annotation.*;
Customer
クラスを宣言し、@XmlRootElement
注釈を使用してルート要素にします。XMLアクセッサ・タイプにFIELD
を設定します。
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer {
firstname
およびlastname
プロパティを定義して、@XmlElement
注釈で注釈を付けます。name=
引数を使用して、XML要素名をカスタマイズします(name=
で名前を明示的に設定していないと、XML要素では、Java属性名が照合されます。たとえば、ここでは、<first-name>
要素の組合せが、<firstName> </firstName>
とXMLに指定されます)。
@XmlElement(name="first-name") private String firstName; @XmlElement(name="last-name") private String lastName;
このオブジェクトは例15-15のようになります。
EclipseLinkのOXMメタデータ形式でマッピングを表す必要がある場合は、例15-16のとおり、eclipselink-oxm.xml
ファイルに定義されているXMLタグを使用して、適切な値を設定する必要があります。
JAXB注釈を使用して、またはEclipseLinkのOXMメタデータ形式でマッピングを表して、XML文書のサブ要素としてネストされているテキスト・ノードに、Javaオブジェクトから値をマップできます。たとえば、<customer>
ルートの<personal-info>
のサブ要素である<first-name>
および<last-name>
要素に値を設定する必要がある場合は、次の手順でマッピングを設定できます。
関連するスキーマに次の要素が定義されていると仮定します。
それ自身がcomplexTypeとして定義されている、タイプcustomer-typeの<customer>
<personal-info>
両方ともタイプstringの<first-name>
と<last-name>
という名前の<personal-info>
のサブ要素
JAXBの注釈を使用すれば、顧客の名と姓の値を適切なXMLサブ要素ノードにマップできます。この例では、単純な要素名のカスタマイズではなく、実際に新しいXML構造が導入されているので、EclipseLinkの@XmlPath
注釈を使用しています。このマッピングを実現するには:
オブジェクトを作成して、javax.xml.bind.annotation.*
およびorg.eclipse.persistence.oxm.annotations.*
をインポートします。
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.*;
Customer
クラスを宣言し、@XmlRootElement
注釈を使用してルート要素にします。XMLアクセッサ・タイプにFIELD
を設定します。
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer {
firstName
およびlastName
プロパティを定義します。
プロパティ宣言の直前の行に@XmlPath
注釈を挿入して、XMLスキーマによって定義されたサブ要素にfirstName
およびlastName
プロパティをマップします。各注釈に対して、適切なXPath述語を指定してマッピングを定義します。
@XmlPath("personal-info/first-name/text()") private String firstName; @XmlPath("personal-info/last-name/text()") private String lastName;
このオブジェクトは例15-17のようになります。
例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; ... }
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番名の名前は顧客の姓を表しています。
次の属性が定義されたXMLスキーマがあるとします。
それ自身がcomplexType
として定義されている、タイプcustomer-typeの<customer>
タイプString
の<name>
この例でも、JAXBの@XmlPath
注釈を使用して、顧客の名と姓を適切な<name>
要素にマップします。また、@XmlType(propOrder)
注釈も使用して、要素が常に正しい位置に配置されるようにします。このマッピングを実現するには:
オブジェクトを作成して、javax.xml.bind.annotation.*
およびorg.eclipse.persistence.oxm.annotations.XmlPath
をインポートします。
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath;
Customer
クラスを宣言して、"firstName"
引数の後に"lastName"
引数が指定された@XmlType(propOrder)
注釈を挿入します。@XmlRootElement
注釈を挿入して、Customer
をルート要素にし、XMLアクセッサ・タイプにFIELD
を指定します。
@XmlRootElement @XmlType(propOrder={"firstName", "lastName"}) @XmlAccessorType(XmlAccessType.FIELD) public class Customer {
タイプString
が指定されたプロパティfirstName
およびlastName
を定義します。
適切なXPath述語が指定された@XmlPath
を挿入して、プロパティfirstName
およびlastName
をXML文書の適切な位置にマップします。
@XmlPath("name[1]/text()") private String firstName; @XmlPath("name[2]/text()") private String lastName;
述語"name[1]/text()"
および"name[2]/text()"
は、特定のプロパティがマップされる<name>
要素を示しています。たとえば、"name[1]/text"
はfirstName
プロパティを最初の<name>
要素にマップします。
このオブジェクトは例15-19のようになります。
例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; ... }
XPath述語の使用方法の詳細は、15.5項「マッピング用のXPath述語の使用」を参照してください。
EclipseLinkには、Java注釈の使用に加えて、JAXB注釈のかわりに使用したり、ソースのJAXB注釈をメタデータのXML表現で置換したりできるeclipselink-oxm.xml
という名前のXMLマッピング構成ファイルが用意されています。すべての標準JAXBマッピング機能を使用できるだけでなく、高度なマッピング・タイプとオプションも使用できます。
XMLのメタデータ表現は、次の場合に便利です。
サード・パーティから入手したなどの理由でドメイン・モデルを変更できない場合。
JAXBのAPIにコンパイル時の依存関係が存在しないようにする必要がある場合(Java SE 6より前のバージョンのJavaを使用している場合)。
複数のJAXBマッピングをドメイン・モデルに適用する必要がある場合(注釈では1つに制限されます)。
他のテクノロジからの多数の注釈がすでにオブジェクト・モデルに含まれているので、さらに追加するとクラスが読みにくくなる場合。
この項では、eclipselink-oxm.xml
を使用してJAXBの注釈をオーバーライドする方法を示します。
注意: このマッピング・ファイルを使用すると多くの高度な機能が有効になりますが、他のJAXBの実装に移植できなくなる場合があります。 |
まず、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>
次に、オブジェクトのJAXBContext
にマッピング・ファイルを渡します。
次のコードを挿入して、外部化されたメタデータを指定します。
Map<String, Object> properties = new HashMap<String, Object>(1); properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "org/example/oxm.xml); JAXBContext.newInstance("org.example', aClassLoader, properties);
JAXBContext
に渡すプロパティ・オブジェクトを作成します。この例では、次のようになります。
Map<String,Object> properties = new HashMap<String,Object>(); properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, metadata);
JAXBContext
を作成します。この例では、次のようになります。
JAXBContext.newInstance("example.order:example.customer", aClassLoader, properties);
この項では、EclipseLinkのMOXy APIでXPath述語を使用して、XML要素の名前を指定する式を定義する方法を説明します。XPath述語とは、オブジェクトからXMLへの特定のマッピングを定義する式のことです。前述の例で示したように、JAXBのデフォルトでは、Javaのフィールド名がXML要素名として使用されます。
この項には次のサブセクションがあります。
前述のとおり、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述語を使用すれば、異なる属性値に対して同じ属性名を使用できます。
このマッピング方法は、15.3.2.4項「位置によるテキスト・ノードへの値のマッピング」に説明されています。
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フィールドをマップする必要があります。
Customer
クラス・エンティティを作成するには:
次のコードを追加して、必要なJPAパッケージをインポートします。
import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath;
Customer
クラスを宣言し、@XmlRootElement
注釈を使用してルート要素にします。XMLアクセッサ・タイプにFIELD
を設定します。
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer {
Customer
クラスに対して、次のプロパティをローカルに宣言します。
firstName
(文字列型)
lastName
(文字列)
Address
(住所)
@XmlPath(
element-name
[@
attribute-name
='
value
'])
をプロパティ宣言の前に記述することにより、各プロパティに対してXPath述語を設定します。たとえば、firstName
の場合は、次の文でXPath述語を設定します。
@XmlPath("node[@name='first-name']/text()")
また、Customer
クラスに対してphoneNumber
プロパティをList<PhoneNumber>
タイプでローカルに宣言し、値new ArrayList<PhoneNumber>()
をそれに割り当てます。
Customer
クラスは例15-22のスニペットのようになります。
例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>(); ... }
Address
クラスを作成するには、次を実行します:
次のコードを追加して、必要なJPAパッケージをインポートします。
import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath;
Address
クラスを宣言して、XMLアクセッサ・タイプにFIELD
を設定します。
@XmlAccessorType(XmlAccessType.FIELD)
public class Address {
Address
クラスがXML文書のルート要素ではないので、このインスタンスには、前述のタスクのように@XmlRootElement
注釈は必要ありません。
String
プロパティstreet
をAddress
クラスに対してローカルに宣言します。注釈@XmlPath("node[@name='street']/text()")
をプロパティ宣言の前に記述して、XPath述語を設定します。
Address
クラスは例15-23のようになります。
PhoneNumber
エンティティを作成するには:
次のコードを追加して、必要なJPAパッケージをインポートします。
import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath;
PhoneNumber
クラスを宣言し、@XmlRootElement
注釈を使用して、クラスをルート要素にします。XMLアクセッサ・タイプにFIELD
を設定します。
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer {
typeおよびstringプロパティを作成し、@XmlAttribute
注釈を使用して、PhoneNumberルート要素の属性としてマッピングを定義します。
@XmlAttribute private String type; @XmlValue private String number;
PhoneNumber
オブジェクトは例15-24のようになります。
セルフ・マッピングは、ターゲット・オブジェクトからのデータがソース・オブジェクトのXML要素内に表示されるようにターゲット・オブジェクトのXPathを"."(ドット)に設定した場合に、1対1のマッピングで発生します。この演習では、15.5.3項「属性値に基づくマッピング」の例を使用して、Addressの情報が、自分の要素にラップされずにcustomer要素に直接表示されるようにマップします。
セルフ・マッピングを作成するには:
15.5.3.1項「タスク1: Customerエンティティの作成」のタスク1と2を繰り返します。
Customer
クラスに対して、次のプロパティをローカルに宣言します。
firstName
(文字列型)
lastName
(文字列)
Address
(住所)
@XmlPath(
element-name
[@
attribute-name
='
value
'])
をプロパティ宣言の前に記述することにより、firstName
およびlastName
プロパティに対してXmlPath注釈を設定します。たとえば、firstName
の場合は、次の文でXPath述語を設定します。
@XmlPath("node[@name='first-name']/text()")
address
プロパティの@XmlPath
に"."(ドット)を設定します。
@XmlPath(".") private Address address;
また、Customer
クラスに対してphoneNumber
プロパティをList<PhoneNumber>
タイプでローカルに宣言し、値new ArrayList<PhoneNumber>()
をそれに割り当てます。
Customerエンティティに対してレンダリングされるXMLは例15-25のようになります。
例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>
動的JAXB/MOXyでは、様々なメタデータ・ソースからJAXBContext
をブートストラップでき、コンパイルされたドメイン・クラスがなくても、使い慣れたJAXBのAPIを使用してデータのマーシャルおよびアンマーシャルを行えます。以前に生成されたJavaソース・コードを更新および再コンパイルしなくてもメタデータを更新できるようになったので、これは静的JAXBに対する拡張機能になります。
動的JAXB/MOXyのエンティティを使用すると、次のような利点があります。
実際のJavaクラス(たとえば、Customer.class
、Address.class
)を使用するかわりに、ドメイン・オブジェクトがDynamicEntity
のサブクラスになります。
動的エンティティには、データを操作するためのget(propertyName)
/set(propertyName propertyValue)
という簡単なAPIが用意されています。
動的エンティティには、メタデータの解析時にメモリー内に生成されるDynamicType
が関連付けられています。
次のタスクで、動的JAXBの使用方法を示します。
この例は、XMLスキーマから動的JAXBContext
をブートストラップする方法を示しています。
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
のいずれかの形式である必要があります。
例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>
DynamicJAXBContext
をcustomer.xsd
スキーマからブートストラップする場合は、エンティティ・リゾルバを渡す必要があります。次を実行します:
インポートされたスキーマの場所を解決するには、例15-30に示されたコードを指定して、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; } }
DynamicJAXBContext
を実装した後、例15-31に示すようにEntityResolver
を渡します。
別のスキーマをインポートする際に、次の例外が表示される場合があります。
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
この例は、動的エンティティを作成して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);
DynamicJAXBContext
から入手したマーシャラは、標準のマーシャラで、DynamicEntityのインスタンスをマーシャルするために通常どおり使用できます。
例15-33 標準の動的JAXBマーシャラ
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);marshaller.marshal(customer, System.out);
例15-34に結果のXML文書を示します。
この例では、「タスク2: 動的エンティティの作成とXMLへのマーシャル」で作成した動的エンティティをXMLからアンマーシャルする方法を示します。参照されているXMLは例15-34に示されています。
DynamicJAXBContext
から入手したアンマーシャラは、標準のアンマーシャラで、DynamicEntity
のインスタンスをアンマーシャルするために通常どおり使用できます。
次に、動的エンティティのどのデータを取得するかを指定します。System.out.println()
を使用して、この値を指定し、エンティティ名で渡します。DynamicEntity
には、たとえば、getName()
のかわりにget("name")
のようなプロパティに基づいたデータ・アクセスが用意されています。
System.out.println(customer.<String>get("name"));
DynamicEntity
のインスタンスには、例15-36に示されているようにDynamicEntity
をイントロスペクトするために使用できる、対応するDynamicType
があります。
この章のソリューションが実装されているその他のテクノロジおよびツールの詳細は、次の参考資料を参照してください。
Oracle TopLinkによるJAXBアプリケーションの開発