レベル: 中級 Stephen B Morris, CTO, Omey Communications
2009年 7月 07日 2 回連載の第 2 回目となるこの記事では、XML のオプション・リレーショナル・データの作成、保管、操作にはどのようなソフトウェアが必要であるかを詳しく調べます。使用するソフトウェアには、JAXB (Java™ Architecture for XML Binding) によって完全に変換されたサンプル・コード、JPA (Java Persistence API)/Hibernate、メモリー内データベース、そして永続化に関連するアノテーションなどが含まれています。
この 2 回連載記事の第 1 回では、JAXB を使って XML ドメインから Java コードに変換する方法を説明しました。このドメイン間変換という概念は、多くの研究分野で役に立ちます。その一例としては、信号処理の分野での時間ドメインから周波数ドメインへの変換が挙げられます。XSLT での変換もこれに関連する例として挙げられ、XSLT ではスタイルシートを使用して XML を他のテキスト・フォーマット (HTML など) に変換します。それではまず、JAXB に必要なセットアップから見ていきましょう。
JAXB のセットアップ
普段、私はソフトウェアのインストールについてはあまり触れませんが、Java Web Services Developer Pack (「参考文献」を参照) をインストールするときには、いつも以上に注意が必要です。そのため、皆さんがすべてをきちんとインストールできるように、インストール・プロセスについて説明しておきます。
まず始めに、バージョン 2.0 のパッケージをダウンロードします。この Web サービス・ダウンロードのサイズは比較的小さいので (23MB)、ダウンロードにはそれほど時間がかからないはずです。この記事を読んだ後、さらに詳細を調べたいのであれば、Web サービス・ドキュメンテーション・パッケージもダウンロードしてください (同じページにあります)。InstallShield ウィザードを実行するには、ダウンロードした実行可能ファイルをダブルクリックします。その後は、ウィザードの指示に従います。私は Web コンテナーのインストールを選択しませんでした。また、インストール・ディレクトリーは C:\Sun\jwsdp-2.0 に変更しました。
 |
よく使われる頭字語
- API: Application Programming Interface
- HTML: Hypertext Markup Language
- SQL: Structured Query Language
- XML: Extensible Markup Language
- XSD: XML Schema Definition
- XSLT: Extensible Stylesheet Language Transformation
|
|
ダウンロードをインストールした後には、極めて重要な作業が待っています。%JAVA_HOME%\jre\lib ディレクトリーのなかに、endorsed という名前を付けたフォルダーを作成してください。次に、C:\Sun\jwsdp-2.0\lib フォルダーの中身を新規に作成したフォルダー %JAVA_HOME%\jre\lib\endorsed に貼り付けます。%JAVA_HOME% 環境変数は、Java ソフトウェア開発キット (JDK) インストールへの絶対パスです。お望みであれば、コマンドラインで java.endorsed.dirs システム・プロパティーを使用することもできますが、新しくディレクトリーを作成する方がいくらか簡単です。
もう 1 つ必要な作業は、Apache Ant をコピーすることです (「参考文献」を参照)。Ant をダウンロードしてインストールし、実行可能プログラムがシステム PATH に設定されていることを確認してください。
ソース・コードの実行
この記事のソース・コードは、「ダウンロード」に .zip ファイルとして含まれています。記事の例に従うには、このファイルの内容を C:\article_code などのフォルダーに解凍してください。すると、このディレクトリーの下に 2 つのフォルダーが作成されます。1 つは unmarshal-read、もう 1 つは dbcode という名前のフォルダーです。
JAXB サンプルの実行
環境が適切に構成されていることを確認するには、サンプル・フォルダーのいずれか (例えば、C:\Sun\jwsdp-2.0\jaxb\samples\unmarshal-read など) で DOS コンソールを開きます。このフォルダーで、リスト 1 に示すように Ant ターゲットを実行してみてください。
リスト 1. Ant の実行
C:\Sun\jwsdp-2.0\jaxb\samples\unmarshal-read>ant -p
Buildfile: build.xml
This sample application demonstrates how to unmarshal
an instance document into a Java content tree and access
data contained within it.
Main targets:
clean Deletes all the generated artifacts.
compile Compile all Java source files
javadoc Generates javadoc
run Run the sample app
Default target: run
|
リスト 1 のようなプログラム出力が表示されたら、セットアップは正常に行われています。ちなみに、ant -p コマンドは、特定の build.xml ファイルでサポートされているターゲットを判断する上で重宝するコマンドです。このコマンドを使用すれば、ターゲットを実行しなくてもビルド・スクリプトの内容を見ることができます。
サンプル・プログラムを実行するには、何もパラメーターを指定せずに ant コマンドを入力します。すると、リスト 2 のプログラム出力が表示されます。
リスト 2. JAXB サンプル・プログラムの実行
C:\Sun\jwsdp-2.0\jaxb\samples\unmarshal-read>ant
Buildfile: build.xml
compile:
[echo] Compiling the schema...
[xjc] C:\Sun\jwsdp-2.0\jaxb\samples\unmarshal-read\gen-src\primer.po is not
found and thus excluded from the dependency check
[xjc] Compiling file:/C:/Sun/jwsdp-2.0/jaxb/samples/unmarshal-read/po.xsd
[xjc] Writing output to C:\Sun\jwsdp-2.0\jaxb\samples\unmarshal-read\gen-src
[echo] Compiling the java source files...
[javac] Compiling 4 source files to
C:\Sun\jwsdp-2.0\jaxb\samples\unmarshal-read\classes
run:
[echo] Running the sample application...
[java] Ship the following items to:
[java] Alice Smith
[java] 123 Maple Street
[java] Cambridge, MA 12345
[java] US
[java]
[java] 5 copies of "Nosferatu - Special Edition (1929)"
[java] 3 copies of "The Mummy (1959)"
[java] 3 copies of "Godzilla and Mothra:
Battle for Earth/Godzilla vs. King Ghidora"
BUILD SUCCESSFUL
Total time: 9 seconds
|
リスト 2 には完全な JAXB プログラムが記載されています。この後すぐに、この JAXB プログラムを使って XML を Java コードに変換する例を紹介します (ここでは用語が重要になるので、用語について説明しておきます。XML を Java コードにアンマーシャリングすると言いますが、マーシャリングとは Java コードを XML に変換すること、そしてアンマーシャリングとは XML を Java コードに変換することです)。次は、オプション要素が含まれる XML データについて見て行きます。
オプション要素が含まれる XSD 仕様
リスト 3 に xsd:complexType 要素を記載します。これがこの記事で検討する要素です。
リスト 3. オプション要素が含まれる XSD の型定義
<xsd:complexType name="PurchaseOrderType">
<xsd:sequence>
<xsd:element name="shipTo" type="EuropeanAddress"/>
<xsd:element name="billTo" type="EuropeanAddress"/>
<xsd:element ref="comment" minOccurs="0"/>
<xsd:element name="items" type="Items"/>
</xsd:sequence>
<xsd:attribute name="orderDate" type="xsd:date"/>
</xsd:complexType>
|
リスト 3 のコードは po.xsd という名前のファイルの一部で、このファイルはこれから説明する XML のスキーマです。リスト 4 に、po.xsd スキーマに従った XML ファイル (po.xml) を記載します。
リスト 4. XML 文書の例
<?xml version="1.0"?>
<purchaseOrder orderDate="1999-10-20">
<shipTo country="IE">
<name>Alice Smith</name>
<street>123 Maple Street</street>
<city>Cambridge</city>
<postcode>12345>/postcode>
</shipTo>
<billTo country="IE">
<name>Robert Smith</name>
<street>8 Oak Avenue</street>
<city>Cambridge</city>
<postcode>12345</postcode>
</billTo>
<items>
<item partNum="242-NO" >
<productName>Nosferatu - Special Edition
(1929)>/productName>
<quantity>5</quantity>
<USPrice>19.99</USPrice>
</item>
<item partNum="242-MU" >
<productName>The Mummy (1959)</productName>
<quantity>3</quantity>
<USPrice>19.98</USPrice>
</item>
<item partNum="242-GZ" >
<productName>Godzilla and Mothra: Battle for
Earth/Godzilla vs. King Ghidora</productName>
<quantity>3</quantity>
<USPrice>27.95</USPrice>
</item>
<>/items>
</purchaseOrder>
|
リスト 4 の XML にはコメント・フィールドが 1 つも含まれていないことに注意してください。リスト 3 に示されているように、コメント・フィールドはオプションです。
ここで、リスト 4 の XML データを 1 つ以上の Java クラスにアンマーシャリングします。それには DOS コンソールを unmarshal-read というソース・コード・フォルダーで開き、以下のコマンドを実行します。
ant compile -Djwsdp.home=C:\Sun\jwsdp-2.0
|
リスト 5 に、予想される出力を記載します。
リスト 5. XML から Java への変換
C:\article_code\unmarshal-read>ant compile -
Djwsdp.home=C:\Sun\jwsdp-2.0
Buildfile: build.xml
compile:
[echo] Compiling the schema...
[mkdir] Created dir: C:\article_code\unmarshal-
read\gen-src
[xjc] C:\article_code\unmarshal-read\gen-
src\primer.po is not found and thus excluded from the
dependency check
[xjc] Compiling file:/C:/article_code/unmarshal-
read/po.xsd
[xjc] Writing output to C:\article_code\unmarshal-
read\gen-src
[echo] Compiling the java source files...
[mkdir] Created dir: C:\article_code\unmarshal-
read\classes
[javac] Compiling 5 source files to
C:\article_code\unmarshal-read\classes
BUILD SUCCESSFUL
Total time: 5 seconds
|
リスト 5 では、JAXB が魔法のように XML ファイルから Java クラスを生成しています。このプロセスを実行しているのが、リスト 6 の Java プログラムです。
リスト 6. アンマーシャリング・プロセスを実行するコード
public static void main( String[] args ) {
try {
// create a JAXBContext capable of handling classes generated into
// the primer.po package
JAXBContext jc = JAXBContext.newInstance( "primer.po" );
// create an Unmarshaller
Unmarshaller u = jc.createUnmarshaller();
// unmarshal a po instance document into a tree of Java content
// objects composed of classes from the primer.po package.
JAXBElement<?> poElement =
(JAXBElement<?>)u.unmarshal( new FileInputStream( "po.xml" ) );
PurchaseOrderType po = (PurchaseOrderType)poElement.getValue();
// examine some of the content in the PurchaseOrder
System.out.println( "Ship the following items to: " );
// display the shipping address
EuropeanAddress address = po.getShipTo();
displayAddress( address );
// display the items
Items items = po.getItems();
displayItems( items );
} catch( JAXBException je ) {
je.printStackTrace();
} catch( IOException ioe ) {
ioe.printStackTrace();
}
}
|
リスト 6 のコードは、以下の主要なステップで構成されています。
- JAXB コンテキストを作成する
- そのコンテキストを使用してアンマーシャラーを作成する
- po.xml ファイルをアンマーシャリングする
PurchaseOrderType クラスのオブジェクトを作成する
- そのオブジェクトからいくつかの要素を抽出して表示する
以上のように、リスト 6 のコードにはかなりの処理内容が詰まっています。元の XML データの Java 表現が用意できたところで、次はこのデータをリレーショナル・データベースに格納します。
Java コードからデータベース・エンティティーへの変換
Java アノテーションがその本領を発揮するのは、Java クラスを永続化させるときです。このような Java コードからリレーショナル・ドメインへのマッピングは、オブジェクト・リレーショナル・マッピング (ORM) と呼ばれます。リスト 7 に、ShippingAddress クラスのマッピング例を記載します。
リスト 7. ShippingAddress の ORM
@Embeddable
public class ShippingAddress {
@Column(name = "SHIPPING_ADDRESS_STREET")
private String street;
@Column(name = "SHIPPING_ADDRESS_CITY")
private String city;
ShippingAddress() {}
public ShippingAddress(String street, String city) {
this.street = street;
this.city = city;
}
public String getStreet() {
return street;
}
private void setStreet(String street) {
this.street = street;
}
public String getCity() {
return city;
}
private void setCity(String city) {
this.city = city;
}
}
|
リスト 7 を見ると、アノテーションのことをひとまず忘れるとしたら、これは ShippingAddress という名前の単純な Java クラスでしかありません。実際、Eclipse などの統合開発環境 (IDE) を使用すれば、リスト 7 のコードの大部分 (つまり、コンストラクターと、セッターおよびゲッター・メソッドの部分) を自動的に生成するのは簡単です。アノテーションは単に、クラスとそのプロパティーをリレーショナル・ドメインにマッピングするためだけに使用されます。上記の例では、@Embeddable アノテーションによって、このクラス全体を別のクラス (リスト 8 の entity クラス) に組み込めることが示されています。
リスト 8. entity クラス
@Entity
@Table(name = "PURCHASE_ORDERS")
public class PurchaseOrder {
@Id @GeneratedValue
@Column(name = "PO_ID")
private Long id;
@Embedded
private ShippingAddress shippingAddress;
@Embedded
private BillingAddress billingAddress;
@Column(name = "COMMENT")
private String comment;
@Column(nullable=false, name="COMMENT_ENTERED",
columnDefinition="boolean default false")
private boolean commentEntered;
// More methods here
}
|
リスト 8 で ShippingAddress クラスのインスタンスを PurchaseOrder クラスに組み込んでいる方法に注目してください。この entity クラスが一体どのようにしてデータベースと結び付くのでしょうか。
データベース・コードの実行
データベース・コードを実行するには、DOS コンソールを dbcode フォルダーで開きます。次に start コマンドを入力して、さらに 2 つの DOS コンソールを作成します。そして DOS コンソールのいずれか 1 つで、ant startdb と入力することで Ant ターゲット を実行します。
このターゲットはメモリー内 HSQLDB (HyperSQL DataBase) を起動します。このコマンドの実行が完了すると、リスト 9 のような出力が表示されるはずです。
リスト 9. HSQLDB の実行
C:\article_code\dbcode>ant startdb
Buildfile: build.xml
startdb:
[java] [Server@1e0be38]: [Thread[main,5,main]]: checkRunning(false)
entered
[java] [Server@1e0be38]: [Thread[main,5,main]]: checkRunning(false) exited
[java] [Server@1e0be38]: Startup sequence initiated from main() method
[java] [Server@1e0be38]: Loaded properties from
[C:\article_code\dbcode\server.properties]
[java] [Server@1e0be38]: Initiating startup sequence...
[java] [Server@1e0be38]: Server socket opened successfully in 62 ms.
[java] [Server@1e0be38]: Database [index=0, id=0, db=file:database/db,
alias=] opened successfully in 313 ms.
[java] [Server@1e0be38]: Startup sequence completed in 375 ms.
[java] [Server@1e0be38]: 2009-04-10 09:54:26.625 HSQLDB server 1.8.0 is
online
[java] [Server@1e0be38]: To close normally, connect and execute SHUTDOWN
SQL
[java] [Server@1e0be38]: From command line, use [Ctrl]+[C] to abort
abruptly
|
リスト 9 の Ant ターゲットを実行した後は、データベース・マネージャーが実行中になりますが、データベースはまだありません。そこで次の作業として、Ant ターゲット schemaexport を実行して、リスト 10 のデータベース・スキーマを作成します。
リスト 10. データベースの作成
C:\article_code\dbcode>ant schemaexport
Buildfile: build.xml
compile:
[mkdir] Created dir: C:\article_code\dbcode\build
[javac] Compiling 4 source files to C:\article_code\dbcode\build
copymetafiles:
[copy] Copying 2 files to C:\article_code\dbcode\build
schemaexport:
[hibernatetool] Executing Hibernate Tool with a JPA Configuration
[hibernatetool] 1. task: hbm2ddl (Generates database schema)
[hibernatetool]
[hibernatetool] drop table PURCHASE_ORDERS if exists;
[hibernatetool]
[hibernatetool] create table PURCHASE_ORDERS (
[hibernatetool] PO_ID bigint generated by default as identity (start
with 1),
[hibernatetool] SHIPPING_ADDRESS_STREET varchar(255),
[hibernatetool] SHIPPING_ADDRESS_CITY varchar(255),
[hibernatetool] BILLING_ADDRESS_STREET varchar(255),
[hibernatetool] BILLING_ADDRESS_CITY varchar(255),
[hibernatetool] COMMENT varchar(255),
[hibernatetool] COMMENT_ENTERED boolean default false not null,
[hibernatetool] primary key (PO_ID)
[hibernatetool] );
BUILD SUCCESSFUL
Total time: 8 seconds
|
リスト 10 の SQL 文、create table PURCHASE_ORDERS に注目してください。これで常駐データベースは用意できたので、Ant ターゲット run を実行することで、このデータベースにデータを挿入することができます。したがって、3 つ目の DOM コンソールでは ant run コマンドを実行します (リスト 11 を参照)。
リスト 11. プログラムの実行
C:\article_code\dbcode>ant run
Buildfile: build.xml
compile:
copymetafiles:
run:
[java] Value of CommentEntered false
[java] 1 purchase order(s) found:
[java] Broad Street
[java] Boston
[java] Comment entered: true
BUILD SUCCESSFUL
Total time: 3 seconds
|
リスト 11 には、データベースに正常にデータが入力されたことが示されています。データベースの内容を確認するには、ant dbmanager コマンドを実行します (別の DOS コンソールから実行してください)。このコマンドによって、図 1 のようなウィンドウが表示されます。
図 1. データベース・マネージャー・アプリケーション
図 1 の単純なグラフィカル・データベース・マネージャー・アプリケーションは、前のステップで作成したデータベースに接続します。データベースに対してクエリーを実行するには、Command > SELECT の順にクリックし、以下の SQL 文を入力してください。
SELECT * FROM PURCHASE_ORDERS
|
これで Execute SQL をクリックすると、図 2 のような内容が表示されます。
図 2. データが入力されたデータベース
図 2 に示されているデータベース列をリスト 6 およびリスト 7 と比較すると、実際に Java コードの内容がデータベースに組み込まれていることがわかります。これはもちろん、ORM 技術のおかげです。現在、図 2 のデータベースには 2 行のデータが含まれています。リスト 12 に、この 2 行のデータを作成する Java コードを記載します。
リスト 12. 2 つのオブジェクトの永続化
PurchaseOrder purchaseOrder = new PurchaseOrder();
purchaseOrder.setShippingAddress(new ShippingAddress("Broad Street",
"Boston"));
purchaseOrder.setBillingAddress(new BillingAddress("Broad Street", "Boston"));
purchaseOrder.setComment("A COMMENT");
purchaseOrder.setCommentEntered(true);
PurchaseOrder purchaseOrder1 = new PurchaseOrder();
purchaseOrder1.setShippingAddress(new ShippingAddress("Broader Street", "New
York"));
purchaseOrder1.setBillingAddress(new BillingAddress("Broader Street", "New
York"));
em.persist(purchaseOrder);
em.persist(purchaseOrder1);
|
リスト 12 のコードでは、PurchaseOrder クラスの 2 つのオブジェクトをインスタンス化します。この 2 つのオブジェクトにデータが入力されたら、em.persist() を呼び出してオブジェクトをデータベースに永続化させています。注意する点として、purchaseOrder オブジェクトには明示的にコメントを挿入していますが、purchaseOrder1 にはコメントがありません。これを念頭に図 2 を見てみると、COMMENT 列のエントリーが null となっている行があります。しかし、対応する COMMENT_ENTERED 列が NULL 値になることは決してありません。この列は True または False のいずれかとなります。
これで、特定の行にコメントが含まれるかどうかを判断するときに、COMMENT 列に NULL 値があるかどうかを調べる必要はなくなりました。代わりに COMMENT_ENTERED 列の値を読み取って、値が True であれば、COMMENT 列に値が含まれていることがわかります。つまり、XML のオプション・データは適切にモデル化されて実装されたということです。
まとめ
XML データからリレーショナル・データに移行するのは複雑なことのように思えるかもしれませんが、その手順はまず XSD および XML ファイルを処理することから始め、これらのファイルを JAXB を使用して対応する Java クラスに変換し、続いて ORM 技術によってデータベースにデータを入力する、たったそれだけのことです。
明らかに、この記事での問題領域はかなり小さいですが、平均的なサイズのエンタープライズ・アプリケーションにスケール・アップした場合には XML オブジェクトが相当な数になるのが通常です。これらのオブジェクトはビジネス・ドメインのオブジェクトなので、XML で定義するのがもちろん理にかなっています。それは 1 つには、XML で定義する場合、プログラマーでなくてもオブジェクトを定義できるからです。そしてもう 1 つの大きな利点として、オブジェクトを XML で定義すると、JAXB を使用して XML を Java エンティティーに完璧に変換することができます。
XML オブジェクトが Java ドメインに変換されると、アノテーションの威力が発揮され、Java クラスに最小限の変更を加えるだけで永続エンティティーにすることができます。そこからは、データをリレーショナル・データベースに取り込むだけの話です。
Java オブジェクト・リレーショナル・アノテーションを巧みに使用することで、簡潔なスキーマ定義の生成に役立ちます。巧みに使用するとはつまり、nullable=false などのアノテーション要素を賢明に使用して、選択された項目だけを「NULL 可能」にするということです。そして COLUMN_ENTERED 値と組み合わせれば、NULL 値が挿入される心配はありません。これは、実際に有効な設計です。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Source code for this article | code.zip | 5700KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | |  | Stephen Morris は、アイルランドを拠点とするフリーランス・ライター兼コンサルタントです。エンタープライズ開発とネットワーク・アプリケーションに幅広い経験を持つ彼は、これまでネットワークを専門とする世界最大規模の企業に協力し、J2EE/J2SE ベースのネットワーク管理システム、課金アプリケーション、金融システム、SNMP エンティティーの移植/開発、ネットワーク・デバイス技術、そしていくつかのモバイル・コンピューティング・アプリケーションなどのプロジェクトに取り組みました。彼はコンピューター・サイエンスで修士号を取得し、ネットワーク管理の分野で 3 つの特許を持っています。著書に『Moving Your Career Up the Value Chain: Building Specialized Development Skills in a Global Economy』がある他、ネットワーク管理やその他の話題に関する数々の記事も書いています。 |
記事の評価
|