ヘッダーをスキップ
Oracle® Fusion Middleware Oracle TopLinkソリューション・ガイド
12c (12.1.3)
E57549-01
  ドキュメント・ライブラリへ移動
ライブラリ
製品リストへ移動
製品
目次へ移動
目次

前
 
次
 

15 XMLへのJPAのマッピング

この章では、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標準をサポートしています。

コンポーネント

関連情報については、次のEclipseLinkおよびJAXBの例を参照してください。

15.1 ソリューションの概要

この章では、XMLにJPAエンティティをマッピングする一般的な方法をいくつか示します。次の例を扱うには、JAXB、MOXy、XMLバインディング、JAXB注釈のオーバーライド方法などのJPAからXMLへのマッピングの全体的な概念を理解している必要があります。次の項では、これらの概念の基礎的なことがらを説明します。

15.1.1 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エンティティのバインディング」にあります。

15.1.2 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)』を参照してください。

http://www.eclipse.org/eclipselink/moxy.php

15.1.3 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を参照してください。

http://wiki.eclipse.org/EclipseLink/FAQ/WhatIsMOXy

15.1.4 XMLデータ表現の理解

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注釈のオーバーライド」を参照してください。

15.2 XMLへのJPAエンティティのバインディング

次の例は、JAXB注釈を使用してJPAエンティティをXMLにバインドする方法を示しています。バインディングの詳細は、15.1.1項「XMLバインディングの理解」を参照し、JAXBの詳細は、15.1.2項「JAXBの理解」を参照してください。

15.2.1 XMLへのJPAリレーションシップのバインディング

次の演習問題は、JAXBを使用して一連のJPAエンティティからXML表現を導出する方法を示しています。このプロセスは「バインディング」と呼ばれます(XMLバインディングの詳細は、15.2項「XMLへのJPAエンティティのバインディング」を参照してください)。これらの例は、次の2つの一般的なJPAリレーションシップをバインドする方法を示しています。

  • 私有のリレーションシップ

  • 共有参照のリレーションシップ

従業員エンティティを、その従業員の電話番号、住所および部門にマッピングしています。

15.2.1.1 タスク1: アクセッサ・タイプの定義およびクラスのインポート

次のすべての例で、同じアクセッサ・タイプFIELDを使用しているので、JAXB注釈@XmlAccessorTypeを使用してパッケージ・レベルでアクセッサ・タイプを定義します。この時点で、必要なクラスもインポートします。

@XmlAccessorType(XmlAccessType.FIELD)
package com.example.model;
 
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;

15.2.1.2 タスク2: 私有のリレーションシップのマッピング

「私有」のリレーションシップは、ターゲット・オブジェクトが1つのソース・オブジェクトのみによって参照されている場合に発生します。このタイプのリレーションシップは、1対1および埋込み、または1対多のどちらの場合にも可能です。

このタスクでは、Employeeエンティティと、AddressおよびPhoneNumberエンティティの間のこれら両方のタイプのリレーションシップに対して双方向のマッピングを作成する方法を示します。

15.2.1.2.1 1対1および埋込みのリレーションシップのマッピング

JPAの@OneToOneおよび@Embedded注釈は、ソース・エンティティの1つのインスタンスのみが、同じターゲット・エンティティ・インスタンスを参照できることを示しています。この例は、EmployeeエンティティをAddressエンティティにマップする方法、およびその逆にマップする方法を示しています。従業員は1つの住所のみに関連付けられるので、1対1マッピングと見なされます。このリレーションシップは双方向、つまりEmployeeEmployee側からも指している必要のあるAddressを指すので、バックポインタにはEclipseLinkの拡張機能@XmlInverseReferenceが使用されています。

1対1および埋込みマッピングを作成するには:

  1. 15.2.1.1項「タスク1: アクセッサ・タイプの定義およびクラスのインポート」の説明に従って、アクセッサ・タイプFIELDがパッケージ・レベルで定義されていることを確認します。

  2. @OneToOne注釈をEmployeeエンティティに挿入することにより、リレーションシップの1方向(この場合は、Addressemployeeプロパティ)をマップします。

        @OneToOne(mappedBy="resident")
        private Address residence;
    

    mappedBy引数は、リレーションシップがresidentフィールドによって所有されていることを示します。

  3. @OneToOneおよび@XmlInverseMapping注釈をAddressエンティティに挿入することにより、戻り方向(この場合は、Employeeaddressプロパティ)をマップします。

        @OneToOne
        @JoinColumn(name="E_ID")
        @XmlInverseReference(mappedBy="residence")
        private Employee resident;
    

    mappedByフィールドは、このリレーションシップがresidenceフィールドによって所有されていることを示します。@JoinColumnは、外部キーが格納される列を示します。

エンティティは、例15-1および例15-2に示すエンティティと同じように指定してください。

15.2.1.2.2 1対多のリレーションシップのマッピング

JPAの@OneToMany注釈は、ソース・エンティティの1つのインスタンスが同じターゲット・エンティティの複数のエンティティを参照できることを示しています。たとえば、1人の従業員に、固定電話、携帯電話、連絡先の電話、勤務先の予備の電話番号など、複数の電話番号がある場合があります。異なる各番号がPhoneNumberエンティティのインスタンスになり、1つのEmployeeエンティティが各インスタンスを指す場合があります。

このタスクでは、従業員をその従業員の電話番号の1つにマッピングして、逆方向にもマッピングします。EmployeePhoneNumberのリレーションシップは双方向なので、この例でも、EclipseLinkの拡張機能@XmlInverseReferenceを使用してバックポインタをマップします。

1対多マッピングを作成するには、次のようにします。

  1. 15.2.1.1項「タスク1: アクセッサ・タイプの定義およびクラスのインポート」の説明に従って、アクセッサ・タイプFIELDがパッケージ・レベルで定義されていることを確認します。

  2. @OneToMany注釈をEmployeeエンティティに挿入することにより、リレーションシップの1方向(この場合は、PhoneNumberのemployeeプロパティ)をマップします。

        @OneToMany(mappedBy="contact")
        private List<PhoneNumber> contactNumber;
    

    mappedByフィールドは、このリレーションシップがcontactフィールドによって所有されていることを示します。

  3. @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")を示しています。

エンティティは、例15-1および例15-3に示すエンティティと同じように指定してください。

15.2.1.3 タスク3: 共有参照リレーションシップのマッピング

共有参照リレーションシップは、ターゲット・オブジェクトが複数のソース・オブジェクトによって参照されている場合に発生します。たとえば、IT、人事、財務などの複数の部門にビジネスが区分されている場合があります。これらの各部門には、職責、賃金水準、勤務先などの異なる複数の従業員がいます。部門および従業員を管理するには、共有参照リレーションシップが必要になります。

XMLのネストを使用して共有参照リレーションシップを安全に表現することはできないので、キー・リレーションシップを使用します。JPAエンティティのIDフィールドを利用するには、EclipseLinkのJAXB @XmlID注釈を文字列以外のフィールドとプロパティに、@XmlIDREFを文字列のフィールドとプロパティに使用する必要があります。

この項では、多対1の共有参照リレーションシップおよび多対多の共有参照リレーションシップのマップ方法を示す例を示します。

15.2.1.3.1 多対1の共有参照リレーションシップのマッピング

多対1のマッピングでは、ソース・エンティティの1つ以上のインスタンスが同じターゲット・エンティティのインスタンスを参照できます。この例は、従業員の複数の電話番号のいずれか1つに従業員をマップする方法を示しています。

多対1の共有参照リレーションシップをマップするには:

  1. 15.2.1.1項「タスク1: アクセッサ・タイプの定義およびクラスのインポート」の説明に従って、アクセッサ・タイプFIELDがパッケージ・レベルで定義されていることを確認します。

  2. @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注釈は、これが、対応する表の主キーになることを示しています。

  3. @OneToManyおよび@XmlInverseMapping注釈をAddressエンティティに挿入することにより、戻り方向(この場合は、PhoneNumberのemployeeプロパティ)をマップします。

        @OneToMany(mappedBy="contact")
        @XmlInverseReference(mappedBy="contact")
        private List<PhoneNumber> contactNumber;
    

    両方の注釈のmappedByフィールドは、このリレーションシップがcontactフィールドによって所有されていることを示しています。

エンティティは、例15-1および例15-3に示すエンティティと同じように指定してください。

15.2.1.3.2 多対多の共有参照リレーションシップのマッピング

@ManyToMany注釈は、ソース・エンティティの1つ以上のインスタンスが、ターゲット・エンティティの1つ以上のインスタンスを参照できることを示しています。DepartmentEmployeeのリレーションシップは双方向なので、この例でも、EclipseLinkの@XmlInverseReference注釈を使用してバックポインタを表現しています。

多対多の共有参照リレーションシップをマップするには、次を実行します:

  1. 15.2.1.1項「タスク1: アクセッサ・タイプの定義およびクラスのインポート」の説明に従って、アクセッサ・タイプFIELDがパッケージ・レベルで定義されていることを確認します。

  2. 次のコードを挿入して、Departmentエンティティを作成します。

    @Entity
    public class Department {
    
  3. 次のコードを挿入して、多対多のリレーションシップおよびエンティティの結合表を、このエンティティに定義します。

        @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")が指定されます。さらに、関連付けの逆側のプライマリ表も指定されます。

  4. 最初のマッピング(この場合は、Departmentemployeeプロパティ)を完了し、次のコードを挿入して、それをこのエンティティの外部キーにします。

        @XmlIDREF
        private List<Employee> member;
    
  5. 15.2.1.2.1項「1対1および埋込みリレーションシップのマッピング」で作成したEmployeeエンティティに、次のコードを挿入して、eIdがJPA (@Id注釈)およびJAXB (@XmlID注釈)の主キーであることを指定します。

        @Id
        @Column(name="E_ID")
        @XmlID
        private BigDecimal eId;
     
    
  6. さらにEmployeeエンティティに、次のコードを挿入して、リターン・マッピングを指定します。

        @ManyToMany(mappedBy="member")
        @XmlInverseReference(mappedBy="member")
        private List<Department> team;
    

エンティティは、例15-1および例15-4に示すエンティティと同じように指定してください。

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

15.2.2 XMLへの複合主キーのバインディング

JPAエンティティに複合主キーがある場合、次の例のように、JAXB注釈およびEclipseLinkの特定の拡張機能を使用してバインドできます。

15.2.2.1 タスク1: XMLアクセッサ・タイプの定義

15.2.1.1項「タスク1: アクセッサ・タイプの定義およびクラスのインポート」の説明に従って、アクセッサ・タイプにFIELDを定義します。

15.2.2.2 タスク2: ターゲット・オブジェクトの作成

ターゲット・オブジェクトを作成するには、次を実行します:

  1. エンティティの複数のフィールドまたはプロパティにマップするためのEmployeeIDというコンポジット主キー・クラスを指定して、Employeeを作成します。

    @Entity
    @IdClass(EmployeeId.class)
    public class Employee {
    
  2. エンティティの最初の主キーeIdを指定して、列にマップします。

        @Id
        @Column(name="E_ID")
        @XmlID
        private BigDecimal eId;
    
  3. 2番目の主キーcountryを指定します。この場合、@XmlIDの注釈を付けられるのは1つのプロパティ(eId)のみなので、@XmlKeyを使用して主キーを指定する必要があります。

        @Id
        @XmlKey
        private String country;
    

    @XmlKey注釈により、ソース・オブジェクトの@XmlJoinNode注釈を介したキーに基づくマッピングを使用して参照されるキーとして、プロパティがマーク付けされます。スキーマ・タイプIDにプロパティをバインドする必要がない点以外は@XmlKey注釈に似ています。これは、@XmlKey注釈の一般的な適用方法です。

  4. 次のコードを挿入して、PhoneNumberEmployeeプロパティの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;
    }
}

15.2.2.3 タスク3: ソース・オブジェクトの作成

このタスクでは、ソース・オブジェクトPhoneNumberエンティティを作成します。ターゲット・オブジェクトに複合キーがあるので、EclipseLinkの@XmlJoinNodes注釈を使用してマッピングを設定する必要があります。

ソース・オブジェクトを作成するには:

  1. PhoneNumberエンティティを作成します。

    @Entity
    public class PhoneNumber {
    
  2. 多対1のリレーションシップを作成して、結合列を定義します。

     @ManyToOne
        @JoinColumns({
            @JoinColumn(name="E_ID", referencedColumnName = "E_ID"),
            @JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY")
       })
    
  3. EclipseLinkの@XmlJoinNodes注釈を使用して、マッピングを設定します。

    @XmlJoinNodes( {
            @XmlJoinNode(xmlPath="contact/id/text()", referencedXmlPath="id/text()"),
            @XmlJoinNode(xmlPath="contact/country/text()", referencedXmlPath="country/text()")
        })
    
  4. 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;
 
}

15.2.3 XMLへの埋込みIDクラスのバインディング

埋込みIDでは、エンティティの主キーを格納する別個のEmbeddable Javaクラスを定義します。これは@EmbeddedId注釈を使用して定義します。埋込みIDのEmbeddableクラスには、基本マッピングを使用してエンティティの各ID属性を定義する必要があります。埋込みIDのEmbeddableのすべての属性は、主キーの一部であると見なされます。この演習では、JPAエンティティに埋込みIDクラスがある場合に、JAXBを使用して一連のJPAエンティティからXML表現を導出する方法を示します。

15.2.3.1 タスク1: XMLアクセッサ・タイプの定義

15.2.1.1項「タスク1: アクセッサ・タイプの定義およびクラスのインポート」の説明に従って、XMLアクセッサ・タイプにFIELDを定義します。

15.2.3.2 タスク2: ターゲット・オブジェクトの作成

ターゲット・オブジェクトはEmployeeというエンティティで、従業員の連絡先電話番号のマッピングが格納されています。このターゲット・オブジェクトを作成するには、DescriptorCustomizerインタフェースを実装する必要があるので、EclipseLinkの@XmlCustomizer注釈を含める必要があります。また、リレーションシップが双方向なので、@XmlInverseReference注釈も実装する必要があります。

ターゲット・オブジェクトを作成するには:

  1. Employeeエンティティを作成します。@IdClass注釈を使用し、EmployeeIDクラスがエンティティの複数のプロパティにマップされるようにします。

    @Entity
    @IdClass(EmployeeId.class)
    public class Employee {
    }
    
  2. idプロパティを定義して、埋込み可能にします。

        @EmbeddedId
        @XmlPath(".");
        private EmployeeId id;
    
  3. 1対多のマッピング(この場合は、PhoneNumberemployeeプロパティ)を定義します。リレーションシップが双方向なので、@XmlInverseReferenceを使用してリターン・マッピングを定義します。mappedBy引数で示されているように、これら両方のリレーションシップはcontactフィールドによって所有されます。

        @OneToMany(mappedBy="contact")
        @XmlInverseReference(mappedBy="contact")
        private List<PhoneNumber> contactNumber;
    

完成したターゲット・オブジェクトは例15-7のようになります。

例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.2.3.3 タスク3: ソース・オブジェクトの作成

この例のソース・オブジェクトには複合キーがあるので、キーが自分自身にマップされるのを防ぐために、フィールド@XmlTransientをマーク付けする必要があります。EclipseLinkの@XmlCustomizer注釈を使用してマッピングを設定します。

ソース・オブジェクトを作成するには、次を実行します:

  1. PhoneNumberエンティティを作成します。

    @Entity
    public class PhoneNumber {
    }
    
  2. 多対1のマッピングを作成して、結合列を定義します。

    @ManyToOne
        @JoinColumns({
            @JoinColumn(name="E_ID", referencedColumnName = "E_ID"),
            @JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY")
        })
    
  3. 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;
 
}

15.2.3.4 タスク5: PhoneNumberCustomizerクラスとしてのDescriptorCustomizerの実装

タスク4で追加したコードは、新しい値に対してXMLObjectReferenceMappingsを作成する必要があることを示していました。これには、DescriptorCustomizerPhoneNumberCustomizerとして実装して、複数のキー・マッピングを追加する必要があります。これを行うには:

  1. DescriptorCustomizerPhoneNumberCustomizerとして実装します。org.eclipse.persistence.oxm.mappings.XMLObjectReferenceMappingを必ずインポートしてください。

    import org.eclipse.persistence.oxm.mappings.XMLObjectReferenceMapping;
     
    public class PhoneNumberCustomizer implements DescriptorCustomizer {
    
  2. 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);
    }
 
}

15.2.4 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バインディングに関する項を参照してください。

15.3 XMLテキスト・ノードへの単純なJava値のマッピング

この項では、単純なJava値をXMLテキスト・ノードに直接マップする方法をいくつか示します。次の例を示します。

15.3.1 属性に対する値のマッピング

この例では、JavaオブジェクトCustomeridプロパティを、<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)メタデータ形式で値を表す方法を説明します。

15.3.1.1 Javaオブジェクトからのマッピング

Javaオブジェクトからこのマッピングを作成するために重要なのは、フィールドをXML属性にマップする@XmlAttribute JAXB注釈です。このマッピングを作成するには:

  1. オブジェクトを作成して、javax.xml.bind.annotation.*をインポートします。

    package example;
     
    import javax.xml.bind.annotation.*;
    
  2. Customerクラスを宣言し、@XmlRootElement注釈を使用してルート要素にします。XMLアクセッサ・タイプにFIELDを設定します。

    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Customer {
    
  3. Customerクラスのidプロパティを属性としてマップします。

       @XmlAttribute
       private Integer id;
    

このオブジェクトは例15-11のようになります。

例15-11 マップされたidプロパティが設定されたCustomerオブジェクト

package example;
 
import javax.xml.bind.annotation.*;
 
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
   @XmlAttribute
   private Integer id;
 
   ...
}

15.3.1.2 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メタデータ形式の詳細は、15.4項「XMLメタデータ表現を使用したJAXB注釈のオーバーライド」を参照してください。

15.3.2 テキスト・ノードへの値のマッピング

EclipseLinkでは、たとえば、単純なテキスト・ノード、単純な順序のテキスト・ノード、サブセットまたは位置別など、様々な種類のXMLテキスト・ノードにJavaオブジェクトから値を簡単にマップできます。これらのマッピングを次の例に示します。

15.3.2.1 単純なテキスト・ノードに対する値のマッピング

JavaオブジェクトのJAXB注釈を使用して、またはEclipseLinkのOXMメタデータ形式でマッピングを表して、Javaオブジェクトから値をマップできます。

15.3.2.1.1 JAXB注釈を使用したマッピング

文字列値を受け入れる<phone-number>という要素が、関連するスキーマに定義されている場合は、@XmlValue注釈を使用して、<phone-number>ノードに文字列をマップできます。次を実行します:

  1. オブジェクトを作成して、javax.xml.bind.annotation.*をインポートします。

    package example;
     
    import javax.xml.bind.annotation.*;
    
  2. PhoneNumberクラスを宣言し、@XmlRootElement注釈を使用して、そのクラスをphone-numberという名前のルート要素にします。XMLアクセッサ・タイプにFIELDを設定します。

    @XmlRootElement(name="phone-number")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class PhoneNumber {
    
  3. Customerクラスのnumberプロパティの前の行に@XmlValue注釈を挿入し、この値を属性としてマップします。

       @XmlValue
       private String number;
    

このオブジェクトは例15-13のようになります。

例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.3.2.1.2 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>
...

15.3.2.2 単純な順序でのテキスト・ノードへの値のマッピング

JAXB注釈を使用して、またはEclipseLinkのOXMメタデータ形式でマッピングを表して、顧客の名と姓のような値の順序を個別の要素としてマップできます。次の手順では、顧客の名と姓の値をマップする方法を示しています。

15.3.2.2.1 JAXB注釈を使用したマッピング

関連するスキーマに次の要素が定義されていると仮定します。

  • それ自身がcomplexTypeとして定義されている、タイプcustomer-typeの<customer>

  • 両方ともタイプstring<first-name>および<last-name>という名前の順序要素。

@XmlElement注釈を使用すれば、顧客の名と姓の値を適切なXMLノードにマップできます。これを行うには:

  1. オブジェクトを作成して、javax.xml.bind.annotation.*をインポートします。

    package example;
     
    import javax.xml.bind.annotation.*;
    
  2. Customerクラスを宣言し、@XmlRootElement注釈を使用してルート要素にします。XMLアクセッサ・タイプにFIELDを設定します。

    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Customer {
    
  3. 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のようになります。

例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.3.2.2.2 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>
...

15.3.2.3 サブ要素のテキスト・ノードに対する値のマッピング

JAXB注釈を使用して、またはEclipseLinkのOXMメタデータ形式でマッピングを表して、XML文書のサブ要素としてネストされているテキスト・ノードに、Javaオブジェクトから値をマップできます。たとえば、<customer>ルートの<personal-info>のサブ要素である<first-name>および<last-name>要素に値を設定する必要がある場合は、次の手順でマッピングを設定できます。

15.3.2.3.1 JAXB注釈を使用したマッピング

関連するスキーマに次の要素が定義されていると仮定します。

  • それ自身がcomplexTypeとして定義されている、タイプcustomer-typeの<customer>

  • <personal-info>

  • 両方ともタイプstringの<first-name><last-name>という名前の<personal-info>のサブ要素

JAXBの注釈を使用すれば、顧客の名と姓の値を適切なXMLサブ要素ノードにマップできます。この例では、単純な要素名のカスタマイズではなく、実際に新しいXML構造が導入されているので、EclipseLinkの@XmlPath注釈を使用しています。このマッピングを実現するには:

  1. オブジェクトを作成して、javax.xml.bind.annotation.*およびorg.eclipse.persistence.oxm.annotations.*をインポートします。

    package example;
     
    import javax.xml.bind.annotation.*;
    import org.eclipse.persistence.oxm.annotations.*;
    
  2. Customerクラスを宣言し、@XmlRootElement注釈を使用してルート要素にします。XMLアクセッサ・タイプにFIELDを設定します。

    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Customer {
    
  3. firstNameおよびlastNameプロパティを定義します。

  4. プロパティ宣言の直前の行に@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;
 
   ...
}
15.3.2.3.2 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>
...

15.3.2.4 位置によるテキスト・ノードへの値のマッピング

複数のノードの名前が同じ場合には、XML文書内の位置を指定して、Javaオブジェクトから値をマップします。これを行うには、属性の名前ではなく、属性の位置に値をマップします。これは、JAXB注釈を使用するか、EclipseLinkのOXMメタデータ形式でマッピングを表すことによって行えます。次の例では、2つの<name>要素がXMLに含まれています。最初の名前は顧客の名を表し、2番名の名前は顧客の姓を表しています。

15.3.2.4.1 JAXB注釈を使用したマッピング

次の属性が定義されたXMLスキーマがあるとします。

  • それ自身がcomplexTypeとして定義されている、タイプcustomer-typeの<customer>

  • タイプString<name>

この例でも、JAXBの@XmlPath注釈を使用して、顧客の名と姓を適切な<name>要素にマップします。また、@XmlType(propOrder)注釈も使用して、要素が常に正しい位置に配置されるようにします。このマッピングを実現するには:

  1. オブジェクトを作成して、javax.xml.bind.annotation.*およびorg.eclipse.persistence.oxm.annotations.XmlPathをインポートします。

    package example;
     
    import javax.xml.bind.annotation.*;
    import org.eclipse.persistence.oxm.annotations.XmlPath;
    
  2. Customerクラスを宣言して、"firstName"引数の後に"lastName"引数が指定された@XmlType(propOrder)注釈を挿入します。@XmlRootElement注釈を挿入して、Customerをルート要素にし、XMLアクセッサ・タイプにFIELDを指定します。

    @XmlRootElement
    @XmlType(propOrder={"firstName", "lastName"})
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Customer {
    
  3. タイプStringが指定されたプロパティfirstNameおよびlastNameを定義します。

  4. 適切な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述語の使用」を参照してください。

15.4 XMLメタデータ表現を使用したJAXB注釈のオーバーライド

EclipseLinkには、Java注釈の使用に加えて、JAXB注釈のかわりに使用したり、ソースのJAXB注釈をメタデータのXML表現で置換したりできるeclipselink-oxm.xmlという名前のXMLマッピング構成ファイルが用意されています。すべての標準JAXBマッピング機能を使用できるだけでなく、高度なマッピング・タイプとオプションも使用できます。

XMLのメタデータ表現は、次の場合に便利です。

この項では、eclipselink-oxm.xmlを使用してJAXBの注釈をオーバーライドする方法を示します。


注意:

このマッピング・ファイルを使用すると多くの高度な機能が有効になりますが、他のJAXBの実装に移植できなくなる場合があります。


15.4.1 タスク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>

15.4.2 タスク2: JAXBContextの使用方法の構成

次に、オブジェクトのJAXBContextにマッピング・ファイルを渡します。

  1. 次のコードを挿入して、外部化されたメタデータを指定します。

    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);
    
  2. JAXBContextに渡すプロパティ・オブジェクトを作成します。この例では、次のようになります。

    Map<String,Object> properties = new HashMap<String,Object>();
    properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, metadata);
    
  3. JAXBContextを作成します。この例では、次のようになります。

    JAXBContext.newInstance("example.order:example.customer", aClassLoader, properties);
    

15.4.3 タスク3: JAXB実装としてのMOXyの指定

MOXyをJAXB実装として使用する必要があります。これを行うには、次を実行します:

  1. jaxb.propertiesファイルをオープンして、次の行を追加します。

    javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
    
  2. ドメイン・クラスが格納されたパッケージにjaxb.propertiesファイルをコピーします。

15.5 マッピングに対するXPath述語の使用

この項では、EclipseLinkのMOXy APIでXPath述語を使用して、XML要素の名前を指定する式を定義する方法を説明します。XPath述語とは、オブジェクトからXMLへの特定のマッピングを定義する式のことです。前述の例で示したように、JAXBのデフォルトでは、Javaのフィールド名がXML要素名として使用されます。

この項には次のサブセクションがあります。

15.5.1 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述語を使用すれば、異なる属性値に対して同じ属性名を使用できます。

15.5.2 位置に基づくマッピング

このマッピング方法は、15.3.2.4項「位置によるテキスト・ノードへの値のマッピング」に説明されています。

15.5.3 属性値に基づくマッピング

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>

このマッピングを実現するには、NameAddressおよびPhoneNumberの3つのクラスを宣言し、element-name[@attribute-name='value']の形式でXPathを使用して、各Javaフィールドをマップする必要があります。

15.5.3.1 タスク1: Customerエンティティの作成

Customerクラス・エンティティを作成するには:

  1. 次のコードを追加して、必要なJPAパッケージをインポートします。

    import javax.xml.bind.annotation.*;
     
    import org.eclipse.persistence.oxm.annotations.XmlPath;
     
    
  2. Customerクラスを宣言し、@XmlRootElement注釈を使用してルート要素にします。XMLアクセッサ・タイプにFIELDを設定します。

    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Customer {
    
  3. Customerクラスに対して、次のプロパティをローカルに宣言します。

    • firstName (文字列型)

    • lastName (文字列)

    • Address (住所)

    @XmlPath(element-name[@attribute-name='value'])をプロパティ宣言の前に記述することにより、各プロパティに対してXPath述語を設定します。たとえば、firstNameの場合は、次の文でXPath述語を設定します。

    @XmlPath("node[@name='first-name']/text()")
    
  4. また、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>();
 
    ...
}

15.5.3.2 タスク2: Addressエンティティの作成

Addressクラスを作成するには、次を実行します:

  1. 次のコードを追加して、必要なJPAパッケージをインポートします。

    import javax.xml.bind.annotation.*;
     
    import org.eclipse.persistence.oxm.annotations.XmlPath;
     
    
  2. Addressクラスを宣言して、XMLアクセッサ・タイプにFIELDを設定します。

    @XmlAccessorType(XmlAccessType.FIELD)
    public class Address {
    

    AddressクラスがXML文書のルート要素ではないので、このインスタンスには、前述のタスクのように@XmlRootElement注釈は必要ありません。

  3. StringプロパティstreetAddressクラスに対してローカルに宣言します。注釈@XmlPath("node[@name='street']/text()")をプロパティ宣言の前に記述して、XPath述語を設定します。

Addressクラスは例15-23のようになります。

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

15.5.3.3 タスク3: PhoneNumberエンティティの作成

PhoneNumberエンティティを作成するには:

  1. 次のコードを追加して、必要なJPAパッケージをインポートします。

    import javax.xml.bind.annotation.*;
     
    import org.eclipse.persistence.oxm.annotations.XmlPath;
     
    
  2. PhoneNumberクラスを宣言し、@XmlRootElement注釈を使用して、クラスをルート要素にします。XMLアクセッサ・タイプにFIELDを設定します。

    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Customer {
    
  3. typeおよびstringプロパティを作成し、@XmlAttribute注釈を使用して、PhoneNumberルート要素の属性としてマッピングを定義します。

        @XmlAttribute
        private String type;
     
        @XmlValue
        private String number;
    

PhoneNumberオブジェクトは例15-24のようになります。

例15-24 属性値に対するPhoneNumberオブジェクトのマッピング

package example;
 
import javax.xml.bind.annotation.*;
 
@XmlAccessorType(XmlAccessType.FIELD)
public class PhoneNumber {
 
    @XmlAttribute
    private String type;
 
    @XmlValue
    private String number;
 
    ...
}

15.5.4 セルフ・マッピング

セルフ・マッピングは、ターゲット・オブジェクトからのデータがソース・オブジェクトのXML要素内に表示されるようにターゲット・オブジェクトのXPathを"."(ドット)に設定した場合に、1対1のマッピングで発生します。この演習では、15.5.3項「属性値に基づくマッピング」の例を使用して、Addressの情報が、自分の要素にラップされずにcustomer要素に直接表示されるようにマップします。

セルフ・マッピングを作成するには:

  1. 15.5.3.1項「タスク1: Customerエンティティの作成」のタスク1と2を繰り返します。

  2. Customerクラスに対して、次のプロパティをローカルに宣言します。

    • firstName (文字列型)

    • lastName (文字列)

    • Address (住所)

  3. @XmlPath(element-name[@attribute-name='value'])をプロパティ宣言の前に記述することにより、firstNameおよびlastNameプロパティに対してXmlPath注釈を設定します。たとえば、firstNameの場合は、次の文でXPath述語を設定します。

    @XmlPath("node[@name='first-name']/text()")
    
  4. addressプロパティの@XmlPathに"."(ドット)を設定します。

        @XmlPath(".")
        private Address address;
    
  5. また、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>

15.6 動的JAXB/MOXyの使用

動的JAXB/MOXyでは、様々なメタデータ・ソースからJAXBContextをブートストラップでき、コンパイルされたドメイン・クラスがなくても、使い慣れたJAXBのAPIを使用してデータのマーシャルおよびアンマーシャルを行えます。以前に生成されたJavaソース・コードを更新および再コンパイルしなくてもメタデータを更新できるようになったので、これは静的JAXBに対する拡張機能になります。

動的JAXB/MOXyのエンティティを使用すると、次のような利点があります。

次のタスクで、動的JAXBの使用方法を示します。

15.6.1 タスク1: XMLスキーマからの動的JAXBContextのブートストラップ

この例は、XMLスキーマから動的JAXBContextをブートストラップする方法を示しています。

15.6.1.1 XMLスキーマからのブートストラップ

DynamicJAXBContextFactoryを使用して、動的JAXBContextを作成します。例15-26は、createContextFromXSD()を使用してDynamicJAXBContextcustomer.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.InputStreamorg.w3c.dom.Nodejavax.xml.transform.Sourceのいずれかの形式である必要があります。

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

15.6.1.3 スキーマのインポート/インクルードの処理

他のスキーマのインポートが格納されたXMLスキーマからDynamicJAXBContextをブートストラップするには、org.xml.sax.EntityResolverを構成し、インポートされたスキーマの場所を解決して、EntityResolverDynamicJAXBContextFactoryに渡す必要があります。

次の例は、customer.xsd (例15-28)とaddress.xsd (例15-29)の2つのスキーマ文書を示しています。次の文を使用して、customer.xsdaddress.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>

15.6.1.4 EntityResolverを実装して渡す方法

DynamicJAXBContextcustomer.xsdスキーマからブートストラップする場合は、エンティティ・リゾルバを渡す必要があります。次を実行します:

  1. インポートされたスキーマの場所を解決するには、例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;
       }
    
    }
    
  2. DynamicJAXBContextを実装した後、例15-31に示すようにEntityResolverを渡します。

    例15-31 EntityResolverを渡す方法

    FileInputStream xsdInputStream = new FileInputStream("src/example/customer.xsd");
    DynamicJAXBContext jaxbContext = 
        DynamicJAXBContextFactory.createContextFromXSD(xsdInputStream, new MyEntityResolver(), null, null);
    

15.6.1.5 エラー処理

別のスキーマをインポートする際に、次の例外が表示される場合があります。

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
    

15.6.1.6 ClassLoaderの指定

アプリケーションの現在のクラス・ローダーをclassLoaderパラメータとして使用します。このパラメータでは、新しいDynamicTypesが生成される前に、指定したクラスが存在することが確認されます。ほとんどの場合、このパラメータにnullを渡して、かわりにThread.currentThread().getContextClassLoader()を使用できます。

15.6.2 タスク2: 動的エンティティの作成とXMLへのマーシャル

この例は、動的エンティティを作成してXMLにマーシャルする方法を示しています。

15.6.2.1 動的エンティティの作成

DynamicJAXBContextを使用して、DynamicEntityのインスタンスを作成します。エンティティとプロパティ名は、静的JAXBを使用していれば生成されたであろうクラスとプロパティ名(この場合は、customeraddress)に対応しています。

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

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

15.6.3 タスク3: XMLからの動的エンティティのアンマーシャル

この例では、「タスク2: 動的エンティティの作成とXMLへのマーシャル」で作成した動的エンティティをXMLからアンマーシャルする方法を示します。参照されているXMLは例15-34に示されています。

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

15.6.3.2 動的エンティティからのデータの取得

次に、動的エンティティのどのデータを取得するかを指定します。System.out.println()を使用して、この値を指定し、エンティティ名で渡します。DynamicEntityには、たとえば、getName()のかわりにget("name")のようなプロパティに基づいたデータ・アクセスが用意されています。

System.out.println(customer.<String>get("name"));

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

15.7 その他の参考資料

この章のソリューションが実装されているその他のテクノロジおよびツールの詳細は、次の参考資料を参照してください。