目次


Castor によるデータ・バインディング

第 4 回 Java オブジェクトを SQL データベースにバインドする

Castor を使って Java オブジェクトを SQL データ・ストアにバインドする

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: Castor によるデータ・バインディング

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:Castor によるデータ・バインディング

このシリーズの続きに乞うご期待。

2010年 3月 30日、読者から寄せられたコメントを基に、著者によってリスト 9 とそれに続く短いコード・セクションの 3 番目の内容が更新されました。更新内容は、いずれも「author.getFirstName() + " " + author.getLastName());」を「lookup.getFirstName() + " " + lookup.getLastName());」に変更したものです。.

大部分の開発者、特に Java 開発者にとって、データ・バインディングはクロージャーやシングルトン、Ajax などと共に、一般的なボキャブラリーの一部となっています。そして、これらの用語と同様で、データ・バインディングも誤った定義をされていることが多いものです。

具体的には、大部分のプログラマーはデータ・バインディングと聞くと、実際には XML のデータ・バインディングのことを考えます。この、XML というわずか 1 語が挿入されるだけで、大部分のプログラマーはデータ・バインディングがもたらす XML 以外の素晴らしい機能と柔軟性、特に Castor の API が使用される場合の機能と柔軟性を見落としてしまいます。つまり、Castor の場合、XML のデータ・バインディングは、Castor が持つさまざまな機能の一部にすぎず、Castor は Java データを XML 文書にバインドできるだけではなく、SQL データベースにもバインドすることができるのです。そこに SQL データ・バインディングが登場します。

SQL データ・バインディングを定義する

SQL データ・バインディングは新しい用語かもしれませんが、その概念は非常に単純です。実際、SQL データ・バインディングを知るためには、もっとおなじみの用語、XML データ・バインディングが何を意味するのか、という点から見るのが最適の方法です。XML データ・バインディングは単純に、XML 文書の中にあるデータ (通常は要素と属性の中に保存されています) と、Java オブジェクト・モデルのプロパティーとの間のマッピングを作成するプロセスです。この両者の間を、マーシャラーとアンマーシャラーを実行することで行ったり来たりすることができます。マーシャラーは Java オブジェクト・モデルの中のデータを取得し、XML 文書の中に保存します。アンマーシャラーは XML 文書のデータを取得し、Java オブジェクト・モデルのプロパティーの中にロードします。

これをベースに考えれば、SQL データ・バインディングが、SQL データベースの中のデータ (スキーマやテーブル、列などに保存されています) と Java オブジェクトの間にマッピングを作成するプロセスであることは驚くにあたりません。マーシャリングとアンマーシャリングというプロセスがあることは同じですが、変換は Java オブジェクトと XML との間で変換が行われるのではなく、Java オブジェクトと SQL の間で行われます。実際、データ・バインディングに関する大部分の記事で XML を SQL で置き換え、また要素データをテーブル・エントリーで置き換えてみると、SQL データ・バインディングとは一体何であるかが理解できるはずです。

SQL データ・バインディングの価値

Java 技術が初めて登場したとき、Java はほとんどおもちゃのような言語でした。その大きな理由は、API が非常に単純であり、またグラフィックスに焦点を絞っていたためです (AWT を覚えていますか)。Java 技術の成熟を示す特徴的なできごとは、JDBC (Java database connectivity) が追加され、SQL データベースへの接続を維持できるようになったことです。唯一問題だったのは (現在も問題ですが)、JDBC が非常に使いにくいことです。JDBC はそれほど複雑ではありませんが、大部分のプログラムにとっては JDBC のおかげで余分な作業が大量に追加されてしまいます。

Castor と SQL データ・バインディングを使用することで、そうした複雑さの大部分を回避することができます。さらに良いことは、XML のコンテキストと SQL のコンテキストの両方でほとんど同じように機能する API が使えることです。また、データ・バインディングのおかげで、アプリケーションは接続に関する詳細をあまり気にする必要がなくなります。コードの中で JDBC の ResultSet や行カウントの処理を考慮する必要はなくなり、その代わりにマーシャリングとアンマーシャリングの単純な呼び出しを数回行うことで、Java オブジェクトと SQL データベースとの間の変換を処理することができます。

SQL データ・バインディングに関しておそらく最も興味深いことは、まだあまり報道されておらず、注目も集めていないことです。これは、プログラマーの大部分が XML に関心を持たないことを考えると、特に興味深い点です (彼らが XML に関心を持たない理由は、冗長すぎるため、あまりにも扱いにくいため、あるいは彼らがバイナリーでのシリアライズを好むためかもしれません)。ただしその同じプログラマー達が、SQL を喜んで受け入れています。実際、SQL がデータの保存と永続化のための正当な技術であるとは考えない、少し真剣さに欠けるようなプログラマーのグループさえ見つけるのは困難なはずです。(皆さんの仲間のうち、リレーショナル・データベースのことを、過大評価されているとか、もてはやされすぎているとか、あるいは重要ではないと考える人は何人いるかを考えてみてください。)

それを考えると、SQL データ・バインディングが XML のデータ・バインディングよりももっと便利なツールであることは、ほぼ間違いありません。エンタープライズ・アプリケーションでは、必ずデータを永続化する (または保存する) 必要があり、データベースにアクセスしてデータを検索するためのコードを作成するのは、面倒な作業です。SQL データ・バインディングは (このシリーズの前回までの記事を読んでいれば) おなじみの API を使っており、その API をすべて SQL データ・ストアに適用することができます。数コマンドで 1 つのテーブル (あるいは複数のテーブル) に書き込むことができ、また数コマンドでデータを取得することができます。

マッピングも相変わらず必要です

SQL データ・バインディングにとって特に重要なことは、Java や SQL の名前に制約されることなく Java オブジェクトから SQL データ・スキーマにマッピングできることです。これは XML の場合よりも重要です (リレーショナル・データベースの構造は、通常は Java オブジェクトの構造とは大幅に異なります)。テーブルは通常、データの集合であり、オブジェクトは通常 1 つの (場合によっては一連の) データを表します。テーブル同士の関係を他のテーブルに至るまで追跡する必要があるのと同様、オブジェクト同士の関係を他のオブジェクトに至るまで追跡する必要があります。しかし Java オブジェクト・モデルには 1 対多の結合テーブルはなく、もちろん多対多の結合もありません。

リレーショナル・データベースの中の複雑さに入り込み始めると、たとえそれが中程度の複雑さであっても、オブジェクト・モデルで設計する場合とは異なる設計をすることになります。SQL から Java オブジェクトへ、そして Java オブジェクトから SQL へのマッピングの作業の多くは、オブジェクトとテーブルの間のマッピングを定義するための作業です。マッピングは非常に高度なものになることもありますが、この記事では SQL データ・バインディングにおけるマッピングがどのように機能するのかについて、基本的な理解ができるように、いくつかの簡単なマッピングを使うことにします。

これは単なる JDO ですね?

そうです。いや、JDO ではないかもしれません。まあ、JDO のようなものです。ここから、話が少しややこしくなります。

Sun には JDO (Java Data Objects) と呼ばれる仕様があります。JDO 1.0 に対する JSR (Java Specification Request) 12 と、JDO 2.0 に対する JSR 243 は、SQL データ・バインディングに関して非常に具体的な方法を定義しています (ただし明示的に「SQL データ・バインディング」と呼ばれているわけではありません)。JDO に関してよく読み、そして次にこの記事の導入部分の何段落かを再度読むと、同じことが書かれているように思うかもしれません。しかし、Castor 版 の JDO (そうです。Castor も Castor 版 の JDO と呼んでおり、そのため一層ややこしくなっています) は Sun の JDO と同じではなく、さらには Sun の JDO との関係すらありません。単にどちらも基本的な目標が同じであるというだけです。

これは大きく混乱しがちな部分なので、もう 1 度説明しましょう。もし Castor の JDO を使う場合には、Sun 標準の API を使うわけではありません。しかし待ってください。これはそう言われて想像するほど悪い状況ではなく、またそれほど明確に区別できるものでもありません。実は Castor の開発者達は、彼らが独自の API の中に組みこんだ機能の多くが Sun のデータ・バインディング API と JDO API に取り入れられるように努力しています。つまり、現在 Castor は標準の API ではありませんが、その機能の多くが、そして Castor の API そのものが、標準化された JDO API になるかもしれないのです。

サンプル環境をセットアップする

XML データ・バインディングの場合には、2 つのエンドポイントは Java オブジェクト・モデルと XML 文書 (実際は、文書を制限する XML スキーマ) です。SQL データ・バインディングの場合は、相変わらず Java オブジェクト・モデルはありますが、もう一方のエンドポイントは SQL スキーマ (つまりいくつかの列を持つ 1 つまたは複数のテーブル) です。この記事で例として使用する、比較的簡単なエンドポイントのセットを見てみましょう。

このシリーズでは Java オブジェクト・モデルを使って本と著者を表現してきたので、この記事でも同じデータ・モデルを利用します。リスト 1Book クラスのコードを示しています。

リスト 1. Book クラス
package ibm.xml.castor;

import java.util.LinkedList;
import java.util.List;

public class Book {

  /** The book's ISBN */
  private String isbn;
  /** The book's title */
  private String title;
  /** The authors' names */
  private List<Author> authors;

  public Book() { }

  public Book(String isbn, String title, List<Author> authors) {
    this.isbn = isbn;
    this.title = title;
    this.authors = authors;
  }

  public Book(String isbn, String title, Author author) {
    this.isbn = isbn;
    this.title = title;
    this.authors = new LinkedList<Author>();
    authors.add(author);
  }

  public void setIsbn(String isbn) {
    this.isbn = isbn;
  }

  public String getIsbn() {
    return isbn;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public String getTitle() {
    return title;
  }

  public void setAuthors(List<Author> authors) {
    this.authors = authors;
  }

  public List<Author> getAuthors() {
    return authors;
  }

  public void addAuthor(Author author) {
    authors.add(author);
  }
}

リスト 2Author クラスのコードを示しています。

リスト 2. 著者を表現するクラス
package ibm.xml.castor;

public class Author {

  private String firstName, lastName;

  public Author() { }

  public Author(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public String getFirstName() {
    return firstName;
  }
  
  public String getLastName() {
    return lastName;
  }

}

これまでの記事で、これらのクラスのいくつかのインスタンスを取り上げ、それらを XML 文書の中に永続化しました。最初の 2 回の記事では、オブジェクト・モデルから XML 文書に直接変換する方法に焦点を当てましたが、オブジェクト・モデルと XML 文書には同じプロパティー名を使いました。第 3 回の記事では Java オブジェクトから XML へ (そしてその逆) の変換に焦点を当てましたが、変換の際に命名規則のフォーマットを変更できる点についても説明しました。

しかし今回の例では、これらのクラスの中にあるすべてのデータを取り上げ、それらのデータを SQL データベースに保存する必要があります。データは要素と属性に属するのではなく、列と行、そしてテーブルに属しています。

サンプルの SQL データベース・スキーマを作成する

ある面で、SQL データベース・スキーマの方が (特に Java オブジェクト・モデルがあまり複雑ではない場合には) Java オブジェクト・モデルへ厳密にマッピングできることが多いものです (この場合がまさにそれです)。ここで、dw_books と dw_authors という 2 つのテーブルについて考えましょう。

dw_books テーブルは dw_authors テーブルを参照するので、dw_authors テーブルを最初に作成するのが最も簡単です。dw_authors テーブルは、各著者の ID、名前 (first name)、および苗字 (last name)、という 3 つの列を持つ必要があります。この記事に従って MySQL でこのテーブルを作成するためには、リスト 3 のコードを使います。

リスト 3. 著者を保持するテーブル
CREATE TABLE 'dw_authors' (
'id' INT NOT NULL ,
'first_name' VARCHAR( 50 ) NOT NULL ,
'last_name' VARCHAR( 50 ) NOT NULL ,
PRIMARY KEY ( 'id' ) ,
INDEX ( 'first_name' , 'last_name' )
);

この記事のすべてのテーブルに dw_ という接頭辞が使われていることに注意してください。これは、authors と books はテーブルの名前として一般的なため、使われている可能性のある他のテーブルと区別するためです。これは多少面倒もしれませんが、こうすることによって、この記事に従ってテーブルを作成しようする際に (実際に皆さんはこの記事に従ってテーブルを作成していると思います)、皆さんのシステムにある他のデータベースとの間で名前の競合が起きるのを防ぐことができます。

次は dw_books テーブルです。このテーブルも非常に単純であり、SQL で構造を作成するための言語である DDL (data definition language) をリスト 4 に示します。

リスト 4. dw_books テーブル用の DDL
CREATE TABLE 'dw_books' (
'isbn' VARCHAR( 13 ) NOT NULL ,
'title' VARCHAR( 200 ) NOT NULL ,
PRIMARY KEY ( 'isbn' ) ,
INDEX ( 'title' )
);

最後に、この 2 つのテーブルを何らかの方法で接続する必要があります。ここで既に 1 つの問題が出現します。本は複数の著者を持つことができ、これは dw_books テーブルと dw_authors テーブルの間に多対多の関係があることを意味します。言い換えると、1 冊の本が複数の著者を持つことができ、1 人の著者が複数の本を執筆することができます。そのため、本を著者に結びつけるためのテーブルが必要であり、これらの接続に多対多の関係を許容する必要があります。

SQL の世界では、これは非常に一般的なことです。つまり各行が本の ISBN 番号と著者の ID を持つ、結合テーブルが必要です。リスト 5 は、このテーブルがどのようなものかを示しています。

リスト 5. 著者と本に対する (多対多の) 結合テーブル
CREATE TABLE 'dw_books_authors' (
'book_isbn' VARCHAR( 13 ) NOT NULL ,
'author_id' INT NOT NULL ,
PRIMARY KEY ( 'book_isbn' , 'author_id' )
);

参照整合性について理解する

参照整合性は SQL 特有の用語であり、データベース内のすべてのデータを、不正なデータや使われないデータがなく、テーブル間の結合という観点で見たときに矛盾が生じないように、正常なデータとして保つことを指しています。この例の場合での参照整合性は、ある本を削除した場合、dw_books_authors テーブルの中の、その本の ISBN を持つすべてのレコードが削除されることを意味します。その本が存在しなくなると、その本の ISBN を持つレコードも存在していてはいけません。同じことは著者についても当てはまります。ある著者が削除されたら、その著者の ID を持つすべてのレコードも同様に削除されなければなりません。

これを実現するためには外部キーを使います。つまり dw_books_authors テーブルでは、book_isbn は dw_books テーブルの中に外部キーとして isbn を持っています。同じ関係が dw_books_authors の author_id と dw_authors の id の間にも成り立ちます。こうすることで、テーブル間の参照整合性を確実なものにすることができます。

しかし大部分のデータベースでは、この方法とは異なる方法で参照整合性を処理し、また MySQL の多くのバージョンでは参照整合性を完全にはサポートしていません。そこでこの記事では、簡単のため、外部キーを使用しません。外部キーを作成したい場合には (それは素晴らしい考えです)、そのデータベースに関するベンダーのドキュメンテーションを調べるか、あるいは近くにいる親切なデータベース管理者に相談してください。

SQL データ・バインディングをセットアップする

たとえ皆さんが XML のデータ・バインディング用に Castor を使ったことがあったとしても、SQL データ・バインディングをセットアップして実行できるようにするためには、おそらくプログラミング環境に対していくつかの変更を加える必要があります (少なくとも追加を行う必要があります)。

Castor の JDO ファイルをクラスパスに追加する

最初に必要なことは、Castor の JDO クラスをクラスパスに追加することです。このシリーズの第 1 回の記事 (「参考文献」のリンクを参照) のインストール手順に従っていれば、Castor のすべての JAR (Java ARchive) ファイルがあるはずです。しかし、JDO 用の JAR ファイルをクラスパスにまだ追加していないかもしれません。その場合は、Castor ディレクトリーの中で castor-1.1.2.1-jdo.jar を見つけ、それをクラスパスに追加します (皆さんのバージョンの方が新しい場合は、そのファイルを追加します)。

クラスパスへの追加は、おそらく既にクラスパスに含めてある、Castor の JAR ファイル (castor-1.1.2.1.jar、castor-1.1.2.1-xml.jar、そして Xerces JAR ファイルや Commons Logging JAR ファイルなど) に加える形で行われます。アプリケーション・サーバーまたは IDE を使用してコードを実行する場合には、それらのクラスパスも同様に変更されていることを確認します。

環境に JDBC ドライバーを追加する

ここでは SQL データベースに接続するので、そのデータベースのベンダー用の JDBC ドライバーが必要です。IBM DB2® および Informix® 用、Oracle 用、MySQL 用、PostgreSQL 用等々の JDBC ドライバーはすべて無料で入手することができます。データベースにアクセスできる何らかのアプリケーションを既に持っている場合には、それらのアプリケーションに JDBC が付属しているかもしれません。JDBC を使うのが初めての場合は、DB2 および Informix やその他いくつかのデータベース・サーバーで正常に動作する DataDirect の JDBC ドライバーへのリンクを「参考文献」セクションで調べてください。DB2 や Informix を使用していない場合は、そのデータベースのベンダー用の JDBC ドライバーを Google で検索して見つけ、JAR をダウンロードし、その JAR もクラスパスに追加します。

この記事のほとんどの例では、MySQL データベースを使用していますが、その場合は、MySQL の Web サイトから無料で入手できる mysql-connector-java-3.0.17-ga-bin.jar が必要です (詳細は「参考文献」を参照)。

クラスをテーブルにマッピングする

SQL データ・バインディングの最も単純な処理を調べてみましょう。この処理では、他のオブジェクトをまったく参照していないオブジェクトを 1 つのテーブルにマッピングします。これは SQL データ・バインディングで行われる最も基本的な操作であり、もっと高度な機能はすべてこの操作のバリエーションであることがわかります。

マッピングを作成する

まず、Author クラスを 1 つの dw_authors テーブルにマッピングします。必要なのは表 1 に示すようなマッピングです。

表 1. Author オブジェクトと dw_authors テーブルの間のマッピング
Java プロパティーSQL 列
firstNamefirst_name
lastNamelast_name

これは単純ですが、明らかに 1 つ問題があり、dw_authors テーブルには id 列もあります。実際、id 列は dw_authors テーブルの主キーなので、id 列が存在していなければなりません。そのため、Author クラスに追加を行う必要があります (リスト 6)。

リスト 6. Author.java への ID のサポートの追加
package ibm.xml.castor;

public class Author {

  private String firstName, lastName;
private int id;

  public Author() { }

  public Author(int id, String firstName, String lastName) {
this.id = id;
    this.firstName = firstName;
    this.lastName = lastName;
  }

public void setId(int id) {
    this.id = id;
  }

  public int getId() {
    return id;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public String getFirstName() {
    return firstName;
  }
  
  public String getLastName() {
    return lastName;
  }

}

これでテーブルを更新すると、Author インスタンスと dw_authors テーブルの行との間のマッピングが表示されます (表 2)。

表 2. マッピング・テーブルへの ID フィールドの追加
Java プロパティーSQL 列
idid
firstNamefirst_name
lastNamelast_name

Castor 用のマッピングを作成する

今度は Castor に対して、どの Java プロパティーをどの SQL 列にマッピングするのかを指示する必要があります。そのためには、このシリーズの第 3 回の記事で XML 用に使用したマッピング・ファイルと似たマッピング・ファイルを使います。リスト 7 は、Author インスタンスを dw_authors の行に変換するために使用するマッピングを示しています。

リスト 7. Author オブジェクトの dw_authors テーブルへのマッピング
<mapping>
  <class name="ibm.xml.castor.Author" identity="id">
    <map-to table="dw_authors" />
    <field name="id" type="int">
      <sql name="id" type="integer" />
    </field>
    <field name="firstName" type="string">
      <sql name="first_name" type="varchar" />
    </field>
    <field name="lastName" type="string">
      <sql name="last_name" type="varchar" />
    </field>
  </class>
</mapping>

これには大した中身はありません。これは Java オブジェクトと XML の場合に使用したマッピング・ファイルに似ています。このファイルを sql-mapping.xml として保存すると、Java オブジェクトのプロパティーをデータベース・テーブルにどうマッピングするのかを Castor に指示できるようになります。

identity 属性

Java と XML の間でのマッピングで見たものに比べ、このマッピング・ファイルで唯一新しい点は、dw_authors テーブルに対する主キー・フィールドを示す identity 属性が使われていることです。おそらく皆さんはこれまで identity 属性を使ったことがないと思いますが、それは多くの場合 Castor が identity 属性を推論してくれていたり、あるいは Castor の動作に identity 属性が必要なかったりしたためです。しかし SQL データ・バインディングでは、この属性によって、ある 1 つのオブジェクトを一意に定義するフィールド (この場合は著者の id フィールド) を Castor に知らせるのです。

SQL アクセス用に Castor を構成する

XML データ・バインディングの世界では、皆さんは XML から Java オブジェクトにマーシャリングするコードやその逆を行うコードを作成する準備ができています。しかし SQL のパラダイムでは、扱わなければならないソフトウェアが、もう 1 つあります。つまりデータベースです。Castor に対して、データベースの場所や、データベースへのログイン方法、そしてどんなマッピング・ファイルを使うのかを指示する必要があります。そのためには構成ファイルを使います。構成ファイルは通常、jdo-conf.xml のような名前です。リスト 8 は MySQL 用に使用する構成ファイルを示しています。

リスト 8. MySQL への接続方法の Castor 用の定義
<?xml version="1.0" ?>
<!DOCTYPE jdo-conf PUBLIC "-//EXOLAB/Castor JDO Configuration DTD Version 1.0//EN"
                          "http://castor.org/jdo-conf.dtd">
<jdo-conf>
  <database name="YOUR-DATABASE-NAME" engine="mysql">
    <driver class-name="com.mysql.jdbc.Driver"
            url="jdbc:mysql://YOUR-DATABASE-SERVER-HOSTNAME/YOUR-DATABASE-NAME">
      <param name="user" value="YOUR-DATABASE-USERNAME"/>
      <param name="password" value="YOUR-DATABASE-PASSWORD"/>
    </driver>
    <mapping href="sql-mapping.xml"/>
  </database>
  <transaction-demarcation mode="local"/>
</jdo-conf>

エントリーの追加、参照、削除

たくさんの設定を行ってきましたが、いよいよデータベースを操作する準備が整いました。これらの構成ファイルを使って構成をロードし、データベースを開き、そしてそのデータベースを操作する必要があります。リスト 9 ではそのすべてを行います。新しい Author インスタンスを作成してデータベースに保存したら、そのインスタンスを参照し、最後にそのインスタンスを削除します。

リスト 9. author に対して SQL データ・バインディングを使う
import ibm.xml.castor.Author;

import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.JDOManager;

public class SQLTester {
  public static void main(String args[]) {
    try {
      JDOManager.loadConfiguration("jdo-conf.xml");
      JDOManager jdoManager = JDOManager.createInstance("bmclaugh");

      Database database = jdoManager.getDatabase();
      database.begin();
      Author author = new Author(1001, "Joseph", "Conrad");
      database.create(author);

      Author lookup = (Author)database.load(Author.class,
                                    new Integer(1001));
      System.out.println("Located author is named " +
        lookup.getFirstName() + " " + lookup.getLastName());
      database.remove(lookup);
      database.commit();
      database.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

これをステップごとに調べてみましょう。

まず、最初に行う必要のある処理として、接続情報をロードし、データベースに接続します。この処理は基本的に SQL データ・バインディングを使用するすべてのアプリケーションに共通なので、大まかに言えば、単にこれらのコマンドを覚えておけばよいだけです (あるいはこの記事にブックマークを付けておけばよいだけです)。

JDOManager.loadConfiguration("jdo-conf.xml");
JDOManager jdoManager = JDOManager.createInstance("bmclaugh");
Database database = jdoManager.getDatabase();
database.begin();

ここでの目標は、エントリーを作成し、参照し、そして削除するための、Database オブジェクトのインスタンスを取得することです。このインスタンスが取得できると、Author の新しいインスタンスを作成することができ、それをデータベースに保存することができます。

Author author = new Author(1001, "Joseph", "Conrad");
database.create(author);

create() を呼び出すと、Castor はマッピング・ファイル (sql-mapping.xml) を使って、このメソッドに SQL データとして渡されるオブジェクトの保存方法を判断します。そして、その SQL データはデータベースに保存され、利用できるようになります。

次に、ID によってオブジェクトを参照します (ここで、あの identity 属性が登場します)。

Author lookup = (Author)database.load(Author.class,
                                    new Integer(1001));
System.out.println("Located author is named " +
lookup.getFirstName() + " " + lookup.getLastName());

load() メソッドを使用し、このメソッドに対して、ロードしたいクラス型と、必要なエントリーに対する ID 値を渡します。load() は、他の任意の Java オブジェクトと同じように使用できる新しいインスタンスを返します (この場合も sql-mapping.xml で定義されているマッピングが使われます)。

下の例は、エントリーを削除し、データベースをプログラムが実行される前と同じ状態にします。

database.remove(lookup);

最後に忘れてはならないのは、これらの変更をコミットし、データベースを閉じる必要があるということです。

database.commit();
database.close();

リレーショナル・データを追加する

ご覧のとおり、クラスからテーブルへの単一のマッピングを処理することはそれほど難しくありません。しかし、もっと現実的な状況 (例えば、Author インスタンスに関連付けられた Book クラスの Java オブジェクト・モデルなど) を処理しなければならなくなると、さらに興味深いことが起こります。

本に対する基本的な SQL マッピングを定義する

Author の場合と同じように、Book クラスのプロパティーをリレーショナル・データベースに永続化させるためのマッピングを sql-mapping.xml で定義する必要があります。まず、いくつかの追加を行います (リスト 10)。これらの追加によって本の基本的なプロパティーを処理します。

リスト 10. マッピング・ファイルに本を追加する
<mapping>
<class name="ibm.xml.castor.Book" identity="isbn">
    <map-to table="dw_books"/>
    <field name="isbn" type="string">
      <sql name="isbn" type="varchar" />
    </field>
    <field name="title" type="string">
      <sql name="title" type="varchar" />
    </field>
  </class>

  <class name="ibm.xml.castor.Author" identity="id">
    <map-to table="dw_authors" />
    <field name="id" type="int">
      <sql name="id" type="integer" />
    </field>
    <field name="firstName" type="string">
      <sql name="first_name" type="varchar" />
    </field>
    <field name="lastName" type="string">
      <sql name="last_name" type="varchar" />
    </field>
  </class>
</mapping>

これらはどれも、驚くようなものではありません。単に Book クラスを取り上げ、マッピング先のテーブル (dw_books) と、このオブジェクトのプロパティー (isbntitle) のマッピング方法を指定しているにすぎません。

多対多の関係を定義する

さて、ここからが興味深い部分です。Castor に対し、本と著者との関係を指定します。この関係が以下のようなものであることを思い出してください。

  • それぞれの本には固有の ISBN があります。
  • それぞれの著者には固有の ID があります。
  • 1 人の著者によって執筆された本が 1 冊ある場合、dw_books_authors には、その本の ISBN と、その著者の ID とを持つエントリーがあります。
  • 1 冊の本は複数の著者を持つことができます (dw_books_authors には、本の ISBN が同じで著者の ID が異なるエントリーが複数あります)。
  • 1 人の著者は複数の本を持つことができます (dw_books_authors には、著者の ID が同じで本の ISBN が異なるエントリーが複数あります)。

まず、この関係を Book クラスの側から検証します。Book のインスタンスの中で、その本の著者を authors プロパティーに追加する必要があります。その逆も必要です。もし Book インスタンスがその本の authors プロパティーの中にデータを持っていたら、これらの著者を各著者の ID を使って保存する必要があります。このプロパティー (authors) を dw_books_authors のエントリーにマッピングしなければなりません。

これを実現するためには、Castor のもう 1 つの field 要素から始めます (リスト 11)。

リスト 11. フィールド要素を使って authors を Books の中の配列にマッピングする
<field name="authors" type="ibm.xml.castor.Author"
       collection="arraylist">
</field>

ここまでは特に新しいことはありません。authors プロパティーをマッピングしており、この場合の型は stringint ではなく、クラスです。このプロパティーがコレクションであり、1 つの値ではないことを、collection="arraylist" を使って Castor に伝えます。

ただしここで、このフィールドに著者の ID を保存すること、またこのフィールドをどのテーブルに保存するのかを指示する必要があります。著者の ID は dw_books テーブルには入らないからです。リスト 12 は多対多のテーブルの名前 (この場合は dw_books_authors) と、この同じテーブルの中にある本の ISBN に対するキーとを指示する方法を示しています。

リスト 12. sql 要素によって many-table と多対多の関係を定義する
<field name="authors" type="ibm.xml.castor.Author"
       collection="arraylist">
<sql name="author_id"
       many-table="dw_books_authors"
       many-key="book_isbn" />
</field>

このロジックを注意深く追ってください。まず、この要素は Book インスタンスの authors プロパティーの値のみに関係することを思い出してください。実際にマッピングされるフィールドは Author のインスタンスであり、このオブジェクトの識別部分 (id) を、author_id という列にマッピングする必要があります。しかしその列は dw_books テーブルの中にはないので、その列があるテーブルは dw_books_authors であることを many-table を使って示します。最後に、many-key 属性は、この Book インスタンスの ISBN も多対多のテーブルに保存するように指示しています。

リスト 13 は、上記をファイルの他の部分と統合した sql-mapping.xml を示しています。

リスト 13. Books に対する多対多のサポートを追加する
<mapping>
  <class name="ibm.xml.castor.Book" identity="isbn">
    <map-to table="dw_books"/>
    <field name="isbn" type="string">
      <sql name="isbn" type="varchar" />
    </field>
    <field name="title" type="string">
      <sql name="title" type="varchar" />
    </field>
<field name="authors" type="ibm.xml.castor.Author"
           collection="arraylist">
      <sql name="author_id"
           many-table="dw_books_authors"
           many-key="book_isbn" />
    </field>
  </class>

  <class name="ibm.xml.castor.Author" identity="id">
    <map-to table="dw_authors" />
    <field name="id" type="int">
      <sql name="id" type="integer" />
    </field>
    <field name="firstName" type="string">
      <sql name="first_name" type="varchar" />
    </field>
    <field name="lastName" type="string">
      <sql name="last_name" type="varchar" />
    </field>
  </class>
</mapping>

マッピングをテストする

リスト 14 は、新しい Author を作成し、次にその著者に関連付けられる Book を作成するように SQLTester プログラムを変更する方法を示しています。

リスト 14. 新しい著者と本を作成する
import java.util.Iterator;

import ibm.xml.castor.Author;
import ibm.xml.castor.Book;

import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.JDOManager;

public class SQLTester {
  public static void main(String args[]) {
    try {
      JDOManager.loadConfiguration("jdo-conf.xml");
      JDOManager jdoManager = JDOManager.createInstance("bmclaugh");

      Database database = jdoManager.getDatabase();
      database.begin();
      Author author = new Author(1001, "Joseph", "Conrad");
      Book book = new Book("1892295490", "Heart of Darkness", author);
      database.create(author);
      database.create(book);
      database.commit();

      database.begin();
      Book lookup = (Book)database.load(Book.class, "1892295490");
      System.out.println("Located book is named " + lookup.getTitle());
      System.out.println("Authors:");
      for (Iterator i = lookup.getAuthors().iterator(); i.hasNext(); ) {
        Author bookAuthor = (Author)i.next();
        System.out.println("  " + bookAuthor.getFirstName() + " " +
                                  bookAuthor.getLastName());
      }
      database.commit();
      database.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

これは非常に単純に見えます。データベースに接続し、新しい著者と新しい本を作成し、そして次に著者と本の両方をデータベースに追加しています (その後 commit() を実行し、接続を閉じています)。

      Database database = jdoManager.getDatabase();
      database.begin();
      Author author = new Author(1001, "Joseph", "Conrad");
      Book book = new Book("1892295490", "Heart of Darkness", author);
      database.create(author);
      database.create(book);
      database.commit();

したがって、他に必要なことは、データベースをチェックしてデータが適切に入力されたかどうかを調べることだけです。

実際のデータの永続化をより適切にシミュレートするためには、データをコミットし、それから新しいトランザクションを開始します (本と著者のデータがコミットされた後、2 番目の database.begin() を実行します)。こうすることによって、Castor が持っているそのデータのローカル・バージョンは使われず、プログラムの残り半分でデータベースに対して実際にクエリーが実行されるようになります。

データを検証する

データが想定と一致しているかどうか確認するために、テーブルの中のデータを覗いてみましょう。dw_books テーブルの中には、表 3 に示すようなデータがあるはずです。

表 3. SQLTester を実行した後の dw_books の中にあるデータ
isbntitle
1892295490Heart of Darkness

表 4 は挿入を行った後の dw_authors テーブルを示しています。

表 4. SQLTester を実行した後の dw_authors の中にあるデータ
idfirst_namelast_name
1001JosephConrad

最後に、表 5 には、本とその本の著者とを結合したものを示していますが、多くの点で、これまで見てきた中で最も重要なデータで構成されています。もし著者と本とを関連付けできないのであれば、これらのデータはどれもあまり役に立ちません。

表 5. SQLTester からデータが入力された、多対多の dw_books_authors テーブル
book_isbnauthor_id
18922954901001

データを削除する

リスト 15 は、SQLTester によって挿入されたデータを削除する簡単なユーティリティー・プログラムです。SQL データ・バインディングをいじりながら本と著者を追加する際に (ぜひ皆さんも実際に追加してみてください)、このクラスを更新することで、追加されたエントリーを削除することができます。クラスを別々に保持することによって、挿入と削除の間でデータベースを検証することができます。こうした検証は、発生する可能性のある問題を学んだりデバッグしたりするための重要な部分です。

リスト 15. SQLTester によって作成されたデータを削除する
import ibm.xml.castor.Author;
import ibm.xml.castor.Book;

import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.JDOManager;

public class SQLClean {
  public static void main(String args[]) {
    try {
      JDOManager.loadConfiguration("jdo-conf.xml");
      JDOManager jdoManager = JDOManager.createInstance("bmclaugh");
      Database database = jdoManager.getDatabase();
      database.begin();
      try {
        Book book = (Book)database.load(Book.class, "1892295490");
        database.remove(book);
      } catch (org.exolab.castor.jdo.ObjectNotFoundException ignored) { }
      try {
        Author author = (Author)database.load(Author.class, 1001);
        database.remove(author);
      } catch (org.exolab.castor.jdo.ObjectNotFoundException ignored) { }
      database.commit();
      database.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

このプログラムでは例外処理がネストされており、データを削除しようとして存在しないデータを参照しにいっている状況に対応しています。これによってプログラムが少しばかり堅牢になっています。

Castor はデフォルトでは関係を永続化しません

SQLTester (リスト 14) を見て気付く興味深い点の 1 つは、Author のインスタンスと Book のインスタンスの両方がデータベースに対して永続化されていることです。実際、この 2 つのいずれかの永続化をやめてしまうと、本または著者の一方を取得することはできますが、もう一方を取得することはできず、また dw_books_authors の中には何もデータが得られません。

Java プログラマーにとって、これが少し奇妙に思えるのは当然です。もし Book インスタンスが Author を持っているなら、この 2 つは結合されているのではないでしょうか。また、本はその本の著者なしでも永続化することができるのでしょうか。それが Castor では永続化することができ、永続化がデフォルトの動作になっているのです。もしこうした関係を Castor に追跡させたいのであれば、以下のコマンドを発行します。

database.setAutoStore(true);

これを、どのトランザクションが作成されるよりも前に実行する必要があります。そこでリスト 16 はこのタスクを、リスト 14 を少し変更したバージョンで処理します。

リスト 16. 関係を自動保存する動作をテストするプログラム
import java.util.Iterator;

import ibm.xml.castor.Author;
import ibm.xml.castor.Book;

import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.JDOManager;

public class SQLTester {
  public static void main(String args[]) {
    try {
      JDOManager.loadConfiguration("jdo-conf.xml");
      JDOManager jdoManager = JDOManager.createInstance("bmclaugh");

      Database database = jdoManager.getDatabase();
database.setAutoStore(true);
      database.begin();
      Author author = new Author(1001, "Joseph", "Conrad");
      Book book = new Book("1892295490", "Heart of Darkness", author);
// We can persist just the book, and Castor will handle the author, as well
      // database.create(author);
      database.create(book);
      database.commit();

      database.begin();
      Book lookup = (Book)database.load(Book.class, "1892295490");
      System.out.println("Located book is named " + lookup.getTitle());
      System.out.println("Authors:");
      for (Iterator i = lookup.getAuthors().iterator(); i.hasNext(); ) {
        Author bookAuthor = (Author)i.next();
        System.out.println("  " + bookAuthor.getFirstName() + " " +
                                  bookAuthor.getLastName());
      }
      database.commit();
      database.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

これを試してみると (データベースの中にまだデータがある場合には、必ず SQLClean を最初に実行します)、先ほどと同じ結果が得られますが、database.create() 呼び出しが 1 つ少ない点が異なります。これは小さな改善ですが、何百冊 (あるいは何千冊) もの本を、その著者と共に追加することを考えてみてください。こうした余分な作業を省略することは非常に重要なのです。

まとめ

XML データ・バインディングは安定しており、変更されるものというよりも追加されるものであることが多いのですが、JDO は Sun のバージョンも Castor のバージョンも共に、まだ変わる可能性のある API です。しかしそれは JDO を避けるべきだという意味ではありません。なぜなら、(どのベンダーによるものであれ) SQL データ・バインディングと JDO によってプログラミングがずっと楽になるからです。この記事で説明した内容を決定的な方法として捉えるのではなく、出発点として使うことで、これらの技術を学んでください。

SQL データ・バインディングを試しながら、それが皆さんのアプリケーションにとって役立つかどうかを判断してください (まず確実に、役に立つはずです)。次に Sun のバージョンの JDO と Castor のバージョンの JDO の両方をチェックし、どちらが好ましいかを調べてください。そして最後に、どちらを使うかを選択します。Castor のバージョンは標準ではありませんが、Sun のバージョンよりも柔軟であり、また Sun とも協力しようと作業しているため、将来は正式な標準に Castor の機能が大量に取り入れられるかもしれません。

さらに重要な点として、より良いプログラミングを行うことができます。SQL データ・バインディングが自分たちの作業にどう役立つのか、どのように作業を減らすことができるか、そして最終的には作業の質や開発するアプリケーションの質をどのように改善できるかを学んでください。同じアプリケーションの中で SQL データ・バインディングと XML データ・バインディングを使ってさまざまなことを試し、両方で同じようなマッピングの仕組みがどのように使われているかを理解してください。そこから大きな成果を得ることができ、そしておそらく、ほとんど完全に JDBC を捨て去ることができるはずです。それが良くないことだと言う人は誰もいないでしょう。


ダウンロード可能なリソース


関連トピック

  • Castor によるデータ・バインディング: 第 1 回 Castor のインストールとセットアップ」(Brett McLaughlin、developerWorks、2007年11月) は、このシリーズの最初の記事として、皆さん自身のマシンで Castor を実行するための第 1 歩として、Castor のダウンロード、インストール、セットアップ、構成、クラスパスの問題、その他の話題について説明しています。
  • Castor によるデータ・バインディング: 第 2 回 XML のマーシャリングとアンマーシャリング」(Brett McLaughlin、developerWorks、2007年12月) は、Java クラスを XML に変換し、そしてその XML を Java コードに逆変換する方法、また Castor がどのように動作するかについて、そして Castor の API でうまく動作するようにクラスを設計する方法について説明しています。
  • Castor によるデータ・バインディング、第 3 回: スキーマ間のマッピング」(Brett McLaughlin、developerWorks、2008年1月) は、扱いにくく不便な XML 文書の中のデータを Castor を使ってカスタムの Java オブジェクトに変換する方法を説明しています。こうすることで、XML 文書の中の要素名や Java クラスのメンバー変数の名前に制約されることがなくなります。
  • Castor のすべてを知るためのオンラインのハブ、Castor の Web サイトを訪れてください。
  • Castor のクラスの JavaDoc を読んでください。
  • Introduction to Castor JDO は、このオブジェクト指向のマッピングとデータ・バインディング・フレームワークについて、適確な紹介をしています。
  • Sun の Java Data Objects のページは正式バージョンの JDO に関して知るための中心的な場です。
  • JSR 12 を訪れ、元々の Java Data Objects である JDO 1.0 を定義した仕様を読んでください。
  • JSR 243 を訪れ、JDO 2.0 の仕様や、JDO を使いやすくし、JDO のデータベース・サポートを標準化し、そして JDO のスコープを広げるための努力について学んでください。
  • 古い記事ですが、developerWorks の「Castor JDOを始めよう」(Bruce Snyder 著、developerWorks、2002年8月) を読んでください。この言語の詳細に関する一部の説明は古くなっていますが、考え方は今でも適用できます。
  • Sun のデータ・バインディング API、JAXB の詳細を説明したシリーズの入門記事「Practical Data Binding」(Brett McLaughlin、developerWorks、2004年5月) を読んでください。
  • developerWorks の XML ゾーンで XML について学んでください。
  • Podcasts: IBM の技術エキスパートから最新の話題を集めてください。
  • MySQL.com から MySQL 用の JDBC ドライバーをダウンロードし、このネイティブ Java JDBC ドライバーについて学んでください。
  • Java and XML, Third Edition』(Brett McLaughlin と Justin Edelson の共著、O'Reilly Media, Inc. 刊) は、データ・バインディングやマッピングの詳細な説明を含めて、XML を最初から最後まで説明しています。
  • Java and XML Data Binding』(Brett McLaughlin 著、O'Reilly Media, Inc. 刊) は古い本ですが、Castor と、データ・バインディングに関係するさまざまな概念の詳細を解説しています。
  • Castor に関する有料のサポートあるいはヘルプを求めている人は、Castor Professional Services を見てください。
  • JDO を試すのにうってつけの DB2 の無料版、DB2 Express-C をダウンロードしてください。IBM DB2 データベースは Sun の標準 JDO や Castor JDO と親和性の高い SQL データベースです。
  • 強力なデータベース・サーバー製品群の製品ラインである IBM Informix を調べてみてください。
  • DataDirect から簡単にダウンロードできる JDBC drivers for DB2 and Informix という便利なパッケージを試してみてください。
static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Java technology
ArticleID=307816
ArticleTitle=Castor によるデータ・バインディング: 第 4 回 Java オブジェクトを SQL データベースにバインドする
publish-date=03302010