連載
» 2006年12月16日 00時00分 公開

小山博史のJavaを楽しむ(3):待望のJava SE 6 でパーシステンス

教育界、技術者コミュニティでJava言語の教育と啓蒙に長年携わってきた筆者が、独自の視点からJavaの面白さを掘り下げていく。(編集部)

[小山博史,ガリレオ]

 Java2 SE 1.4Java SE 5の間には、機能差があり過ぎたため、現場で導入ができずに、これまで移行を見送ってきた読者も多いことでしょう。しかし、今回のJava SE 6(開発コード名:Mustang)の登場で、そろそろ Java2 SE 1.4 から Java SE 5 もしくは 6 へ移行してもいい時期になりました。Java SE 5 が登場したときほどの機能追加はありませんが、Java SE 6 にも注目の機能がいくつかあります。

 今回は、注目度が高いパーシステンス永続化)に関係するものについて紹介しましょう。ただし、本稿執筆時点(2006年12月5日)ではJava SE 6はRC(Release Candidate)版ですので、リリース版では違っている点があるかもしれません。あらかじめご了承ください。

Java DBとは

 パーシステンスによく利用されるのは、リレーショナルデータベース(RDB)です。JDK5まではRDBを別途用意する必要があったので、環境を用意するのにもちょっとした手間が掛かりました。 教育の現場では、学生が自分のパソコンで自習できるようにオープンソースのRDBなどを紹介することが多いですが、インストールに手間が掛かったり、環境設定がちょっと難しかったりすると、肝心のRDBを使うところまでいけないということになってしまうことがあります。オープンソースのRDBは大体UNIX系OS用に開発されてきたものが多いので、Linuxであれば最初に全部のパッケージを選択してインストールすれば簡単に用意することができます。しかし、なかなか勉強のためだけにLinuxマシンを用意するというのも難しく、RDBを使えるようになる前にLinuxに慣れる必要があるため、ハードルが高くなっているのが実情でしょう。これは、DBを使ったプログラミングについて勉強をしたいと考えているビジネスマンにとっても同じです。

 こういったことから、筆者はマルチプラットフォームで動作するJavaの特性を利用したRDBへの期待を大きく持っていたので、HSQLDBのようなJavaで実装されたRDBを知ったときにはすぐに使って、学校での授業へも導入しました。このRDBはJBossなどに標準で同梱されているので、知っている読者も多いでしょう。

 教育や学習といった点だけでなく、最近のJavaが目指してきたEoDEase of Development)という観点からも、開発環境であるJDKをインストールすれば、RDBもすぐに使えるというのは非常に大きな意味があります。いくつものソフトウェアをダウンロードしてきてインストールと設定をするのはやはり面倒です。また、GUIアプリケーションやWebベースシステムが普及して、マルチスレッドプログラミング、ネットワークプログラミングが当たり前となってしまった現在では、排他制御や同期といったことを意識して、なおかつ障害が発生しないようなプログラミングが強く要求されるようになってきています。

 RDBを使えば、これらについてわざわざ自分で実装する必要もなく、少ない労力で安定したプログラムを作成することができます。いまや、OSへの依存度が高いファイルシステムを直接使って、データを書き出したり、読み込んだりといった操作をするプログラムよりも、RDBを操作するプログラムを記述する機会の方が多いのではないでしょうか。こういった点からも、Java DBは大歓迎です。

 さてここで、JDK6にはHSQLDBのようにJavaで実装されたRDBが標準で同梱されます。JDKのドキュメントではJava DBという名前で紹介されています。その実体は Apache License, Version 2.0.のライセンスで配布されているApache Derby データベースです。筆者はこの情報を得て、すぐにApache Derbyのドキュメントを参照したのですが、よく調べてみるとJava DBのドキュメントも用意されていることが分かりました。先にJava DBの方に気が付けば良かったと、ちょっと後悔をしながらJava DBの方も読んでみたところ、どちらもほとんど同じことが書いてありました。Apache Derbyのサイトで得た知識が無駄にはならず、助かりました。

 しかし、Java SE 6 Release Notes - Features and Enhancements -に書いてあることには注目しておいた方がいいでしょう。そこには、「Java DB 10.2 JDBC4 Early Access」と書いてあり、JDK6 RC版のJava DBはJDBC4のEarly Access版で、Apache Derby 10.2.1.7 のソースコードを使ってビルドしているとあります。注意書きもしてあり、製品には使用しない方が良いようです。JDK6 Beta版の時点では、JDBC4版のバイナリは同梱されていなかったことを考えると、JDBC4対応は進んでいるようですが、実際のJDK6リリース版ではこれがどうなっているのか、気になるところです。

編集部注:2006年12月11日にJava SE 6 が正式リリースされましたが、JDBC4がEarly Access版である点は変更なしのようです。

Java DBの使い方

 Java DBはクライアントサーバ方式のRDBでもアプリケーション組み込みのRDBとしても利用できる便利なRDBです。簡単に使い方を紹介しましょう。

 $JAVA_HOMEをJDKをインストールしたディレクトリだとすると、Java DB関連のファイルは$JAVA_HOME/dbにあります。Java DBを使うときは、このディレクトリを環境変数DERBY_HOMEに設定しておくといいでしょう。サーバクライアント方式で使う場合は、$DERBY_HOME/frameworks/NetworkServerの方を使い、組み込み方式で使う場合は、$DERBY_HOME/frameworks/embeddedの方を使います。

 なお、Java DBにはij.ksh(ij.bat)というコマンドラインツールが付属していて、SQL文を直接使ってRDBを操作することもできます。

 ここでは、Linuxでの、バージョンの確認方法だけ紹介しておきます。Windowsでも、環境変数JAVA_HOME、DERBY_HOME、PATHを設定してコマンドプロンプトからsysinfo.batと入力すれば、同じような出力が出てくるはずです。この情報からJDBC 4.0ドライバが動作していることが分かります。

実行例
$ export JAVA_HOME=/usr/java/jdk
$ export DERBY_HOME=/usr/java/jdk/db
$ PATH=$JAVA_HOME/bin:$JAVA_HOME/db/frameworks/embedded/bin:$PATH
$ sysinfo.ksh
------------------ Java 情報 ------------------
Java バージョン: 1.6.0-rc
Java ベンダー: Sun Microsystems Inc.
Java ホーム: /usr/java/jdk1.6.0/jre

(略)

java.specification.name: Java Platform API Specification
java.specification.version: 1.6
--------- Derby 情報 --------
JRE - JDBC: Java SE 6 - JDBC 4.0
[/usr/java/jdk1.6.0/db/lib/derby.jar] 10.2.1.7 - (453926)
[/usr/java/jdk1.6.0/db/lib/derbytools.jar] 10.2.1.7 - (453926)

(略)

 Java DBを組み込み方式で使うときは、従来の「Class.forName("org.apache.derby.jdbc.EmbeddedDriver")」というJDBCドライバを読み込むためのおまじないは必要ありません。直接接続を開始することができます。

 なお、Java DBではデータベースの接続用URLへ属性を指定することができます。ここでは、create=trueとして接続時にDBがない場合は、作成をし、既存のDBがある場合には、そのまま接続をするようにしています。この例では、db/sampleとDB名を指定しているので、プログラムを実行したカレントディレクトリにdbディレクトリとその中にsampleディレクトリが作成され、そこにDB用のファイルが作成されます。絶対パスでDB名を指定することもできます。

java.sql.Connection con = DriverManager.getConnection("jdbc:derby:db/sample;create=true");

 組み込み方式のRDBでは、アプリケーション終了時にRDBも正常終了するように気を使う必要があります。Java DBでは、次のようにデータベースの接続用URLへshutdown属性を指定してRDBが正常にシャットダウンするようにします。この処理によりRDBはコネクションを強制終了するため、必ずSQLExceptionを発生させる点には注意しましょう。

DriverManager.getConnection("jdbc:derby:;shutdown=true");

 サーバクライアント方式で使う場合は、$DERBY_HOME/frameworks/NetworkServer/bin にあるツールを使います。startNetworkServer.kshで起動、stopNetworkServer.kshで停止になります。Windows用に対応するバッチファイルもあります。

 注意点としては、クライアントプログラムでは使用するドライバがorg.apache.derby.jdbc.ClientDriverになりますし、クラスライブラリも derbyclient.jar を使うことになりますから、組み込み式とは別物として意識する必要があります。

Persistence APIを使ってみる

 さて、本題のパーシステンスAPIです。この機能はEJB 3.0という視点から注目を浴びやすい機能ですが、Standard Editionにも取り込まれた機能なので、これからはどんどん利用したいところです。ファイル操作をするよりはRDBを使った方が楽ですが、アノテーションのおかげで、パーシステンスAPIもかなり楽に使えるようになりました。なお、JDKには標準では付いていないので、実装は別途用意する必要があります。パーシステンスAPIの実装には、Hibernate 3.2BEAkodoに由来するOpenJPAや、OracleTopLink JPAといったものがあります。

編集部注:Hibernateについての詳細を知りたい読者は、「Hibernateを試すための準備」をご参照ください。

 それでは、せっかくJDK6にJava DBという便利なRDBが付いたので、これとHibernate 3.2によるJava SE でのパーシステンスAPI利用をしてみました。必要なものは、Hibernate-3.2.1.GA、Hibernate-annotations-3.2.0.GA、Hibernate-entitymanager-3.2.0.GAになります。これらをダウンロード後、展開して必要なJARファイルを手に入れます。普通はAntのビルドファイルを使うのですが、使っているJARファイルが分かりやすいように、ここでは次のようなLinuxのシェルスクリプトでコンパイルを用意しました。実行もこれと似たようなスクリプトを用意して実行できます。プログラミングが簡単になる分、プログラムが自動的に行うことが増えているので、必要なJARファイルもたくさんあります。

コンパイル用シェルスクリプト
#! /bin/sh

export JAVA_HOME=/usr/java/jdk
export DERBY_HOME=$JAVA_HOME/db

CLASSPATH=.
CLASSPATH=$CLASSPATH:./lib/antlr-2.7.6.jar
CLASSPATH=$CLASSPATH:./lib/asm-attrs.jar CLASSPATH=$CLASSPATH:./lib/asm.jar
CLASSPATH=$CLASSPATH:./lib/c3p0-0.9.0.jar
CLASSPATH=$CLASSPATH:./lib/cglib-2.1.3.jar
CLASSPATH=$CLASSPATH:./lib/commons-collections-2.1.1.jar
CLASSPATH=$CLASSPATH:./lib/commons-logging-1.0.4.jar
CLASSPATH=$CLASSPATH:./lib/dom4j-1.6.1.jar
CLASSPATH=$CLASSPATH:./lib/ejb3-persistence.jar
CLASSPATH=$CLASSPATH:./lib/hibernate-annotations.jar
CLASSPATH=$CLASSPATH:./lib/hibernate-entitymanager.jar
CLASSPATH=$CLASSPATH:./lib/hibernate3.jar
CLASSPATH=$CLASSPATH:./lib/javassist.jar
CLASSPATH=$CLASSPATH:./lib/jboss-archive-browsing.jar
CLASSPATH=$CLASSPATH:./lib/jta.jar
CLASSPATH=$CLASSPATH:$DERBY_HOME/lib/derby.jar
CLASSPATH=$CLASSPATH:$DERBY_HOME/lib/derbytools.jar

export CLASSPATH

$JAVA_HOME/bin/javac sample/Sample.java sample/SampleEntity.java

 Java SE でパーシステンスAPIを使うためには、クラスパスのルートに次のようなMETA-INF/persistence.xmlファイルを用意します。今回は、hibernate.cfg.xmlを参照するような書き方をしてみました。ディレクトリ構成は次のようになります。SampleEntityクラスがパーシステンスをする対象で、Mainクラスでパーシステンスのサンプル処理を記述しています。

ディレクトリ構成
+ META-INF/
| + persistence.xml
+ hibernate.cfg.xml
+ lib/
| + (省略)
+ sample/
+ Main.java
+ Main.class
+ SampleEntity.java
+ SampleEntity.class

META-INF/persistence.xml
<?xml version="1.0" encoding="UTF-8" ?>
    <persistence
        xmlns="http://java.sun.com/xml/ns/persistence"
        version="1.0">
        <persistence-unit
             name="sample" transaction-type="RESOURCE_LOCAL">
            <properties>
                <property name="hibernate.ejb.cfgfile"                     value="/hibernate.cfg.xml"/>
            </properties>
        </persistence-unit>
    </persistence>

hibernate.cfg.xml
<!DOCTYPE hibernate-configuration SYSTEM
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

    <hibernate-configuration>
        <session-factory>
            <property
                name="hibernate.connection.driver_class">
                org.apache.derby.jdbc.EmbeddedDriver
            </property>
            <property name="hibernate.connection.url">
                jdbc:derby:db/samplejpa;create=true
            </property>
            <property name="hibernate.dialect">
                org.hibernate.dialect.DerbyDialect
            </property>
            <property name="show_sql">true</property>
            <property name="format_sql">true</property>
            <property name="hibernate.hbm2ddl.auto">
                create
            </property>
            <mapping class="sample.SampleEntity"/>
        </session-factory>
    </hibernate-configuration>

Main.java
package sample;

import java.util.*;
import javax.persistence.*;

public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("sample");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        em.persist(new SampleEntity("JDK6"));
        tx.commit();
        em.close();
        EntityManager em1 = emf.createEntityManager();
        EntityTransaction tx1 = em1.getTransaction();
        tx1.begin();
        String q = "select e from SampleEntity e";
        List entities = em1.createQuery(q).getResultList();
        for (Object o : entities) {
            SampleEntity e = (SampleEntity)o;
            System.console().printf("%s\n", e.getName());
        }
        tx1.commit();
        em1.close();
        emf.close();
    }
}

SampleEntity.java
package sample;

import javax.persistence.*;

@Entity
@Table(name = "ENTITIES")
public class SampleEntity {
    @Id
    @GeneratedValue
    @Column(name = "ID")
    private Long id;
    @Column(name = "NAME")
    private String name;

    private SampleEntity() {}
    public SampleEntity(String name) { setName(name); }
    public Long getId() { return id; }
    private void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

 詳細な情報はhibernate.cfg.xmlに書きましたが、ここで指定するドライバクラスなどは使用するRDBに依存します。HibernateはApache Derbyをサポートしているので、Java DBを使う場合には、Apache Derbyのものが利用できます。最近のEoDの流れで、Hibernateでもエンティティの値を保存するためのテーブルがない場合に、Hibernateへ自動で作成するように設定をすることができます。hibernate.hbm2ddl.autoプロパティでcreateを指定しておくだけで実現できてしまうので、これまでテーブルを作成するためのDDLを用意したり、テーブル構造とクラスのソースコードを同期していた苦労がうそのようです。

 次に実行例の一部を示しますが、Hibernateが動作してたくさんの情報が出力されています。この途中で、Apache Derby Embedded JDBC Driverが使われていることが分かるメッセージが出力されています。最後の方で警告が出ているのが気になりますが、これはデータベースへの1回目の接続時にDBが作られて、2回目の接続で発生しているだけです。1度実行したら、データベースの接続URLにあるcreate=trueの指定を取り除けば発生しなくなります。途中にJDK6と出力されているのがパーシステンスでDBへ1回保存してから、また読み込んで表示しているエンティティの情報になります。

実行例
$ ./run.sh
2006/12/05 3:20:27 org.hibernate.ejb.Version <clinit>
情報: Hibernate EntityManager 3.2.0.GA
2006/12/05 3:20:27 org.hibernate.cfg.annotations.Version <clinit>
情報: Hibernate Annotations 3.2.0.GA
2006/12/05 3:20:27 org.hibernate.cfg.Environment <clinit>
情報: Hibernate 3.2.1

(略)

2006/12/05 3:20:35 org.hibernate.cfg.SettingsFactory buildSettings
情報: JDBC driver: Apache Derby Embedded JDBC Driver, version: 10.2.1.7 - (453926)
2006/12/05 3:20:38 org.hibernate.util.JDBCExceptionReporter logWarnings
警告: データベース 'db/samplejpa' は作成されませんでした。代わりに既存のデータベースに接続されました。

(略)

JDK6

(略)

$

 念のため、Java DBに付属のdblookコマンドを使ってテーブルが作成されていることを確認してみたところ、次のように作成されていました。

実行例
$ /usr/java/jdk/frameworks/embedded/bin/dblook.ksh \
-d jdbc:derby:db/samplejpa
-- タイム・スタンプ: 2006-12-05 03:28:10.041
-- ソース・データベース: db/samplejpa
-- 接続 URL: jdbc:derby:db/samplejpa
-- appendLogs: false

-- ----------------------------------------------
-- 表用の DDL ステートメント
-- ----------------------------------------------
CREATE TABLE "APP"."ENTITIES" ("ID" BIGINT NOT NULL, "NAME" VARCHAR(255));
CREATE TABLE "APP"."HIBERNATE_UNIQUE_KEY" ("NEXT_HI" INTEGER);

(略)

 パーシステンスAPIも基本から説明をすると長いですし、きちんと使えるようになるには詳細を理解する必要があります。しかし、こうやって新しいJDKがリリースされる時期に合わせて、既存技術がどのように進化したのか、どんな新しい技術が普及しつつあるのか、を確認するという意味でほんの触りだけでも触れてみると結構楽しいものです。

console()メソッド

 最後にちょっとしたTipsを紹介しておきます。JDK6からjava.lang.Systemクラスにconsole()メソッドが追加されました。

 例えば、「System.console().printf("%s\n", e.getName());」(上記Main.java赤字)のように使います。これまで、inやoutなどを使っていましたが、このメソッドのおかげでキャラクタベースの入出力が簡単にできるようになりました。

 こうしたちょっとした機能追加もうれしいところです。ジェネリック型アノテーションの導入などでJavaも複雑になってきていますが、機能を理解するとプログラミングがいままでよりも楽しくできることに気が付きます。Javaって本当に面白いですね

編集部注:今回使用したソースはここからダウンロードできます。

編集部注:ジェネリック型アノテーションについての詳細は、「J2SE 5.0「Tiger」で何が変わるか?」をご参照ください。

筆者プロフィール

小山博史(こやま ひろし)

Webシステムの運用と開発、コンピュータと教育の研究に従事する傍ら、オープンソースソフトウェア、Java技術の普及のための活動を行っている。Ja-Jakartaプロジェクトへ参加し、コミッタの一員として活動を支えている。また、長野県の地域コミュニティである、SSS(G)bugs(J)の活動へも参加している。



Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。