この記事では、最新のサーバー・サイドの Java 技術と、リッチなユーザー・インターフェースを作成するための Dojo ツールキットを使用して、データ中心の Web アプリケーションを開発します。これらの技術によって、作成しなければならないコードの量がサーバー・サイドとクライアント・サイドの両方で大幅に減ります。この記事を最大限に活用する上では、Java と JavaScript を熟知していると望ましいです。サンプル・アプリケーションを実際に作成するために必要なのは、コードをコンパイルおよび実行するための Java 1.6 JDK (この記事では JDK 1.6.0_20 を使用しました)、そして Java Web コンテナーです (この記事で使用したのは Apache Tomcat 6.0.14 です)。データ・パーシスタンスに関しては、JDCB 2.0 準拠のドライバーを使用するデータベースであれば、どれでも使用することができます。説明が複雑にならないように、この記事では組み込みデータベースの Apache Derby 10.6.1 を使用しました。この記事で使用する JAX-RS (Java API for RESTful Web Services) の実装は Jersey 1.3 で、JPA (Java Persistence API) の実装は Hibernate 3.5.3 です。最後に付け加える点として、この記事では Dojo ツールキット 1.4 も使用しています。これらのツールへのリンクについては、「参考文献」を参照してください。
Java Persistence API によるオンザフライのデータ
多くの Web アプリケーションはデータを中心としています。つまり、永続データを提示し、ユーザーがこのデータを作成あるいは更新できるようにしているということです。単純そうに聞こえますが、データベースからのデータの読み取り、あるいは書き込みといった基本的な操作でさえ、データ中心のアプリケーションでは面倒な作業になり得ます。けれども、作成しなければならない厄介なボイラープレート・コードは、JPA (Java Persistence API) を使用することによって量が激減します。ここからは、JPA を使用する単純な例を見ていきます。
この記事では、青少年サッカー・リーグを管理するための単純なアプリケーションを開発します。最初に行うのは、リーグに参加しているチームと、それぞれのチームに所属するプレイヤーを追跡する単純なデータ・モデルを開発するという作業です。このデータには常に JPA を使ってアクセスすることになります。まずは、2 つのデータ・モデルのうち、Team から取り掛かります。リスト 1 に、このクラスを記載します。
リスト 1.
Team データ・モデル・クラス
@Entity
public class Team {
....
....@Id
....@GeneratedValue(strategy = GenerationType.IDENTITY)
....private long id;
....
....private String name;
....
....@OneToMany
....private Collection<Player> players;
....
// getters and setters........
}
|
これは JPA アノテーションを使用した典型的なクラスです。まず初めに、@Entity アノテーションを使用して、このクラスがデータベースにマッピングされることを宣言します。オプションで、このクラスに対応するテーブルの名前を指定したり、このクラスと同じ名前を使用する場合の規則を実装したりすることもできます。次に、クラスの id フィールドにアノテーションを付けます。このアプリーションではこのフィールドをテーブルの主キーにしたいため、@Id アノテーションを使ってこれが主キーであることを宣言します。ビジネス・ロジックの観点からすると、id は重要ではありません。データベースのために必要なだけです。値の生成をデータベースに処理させるため、ここでは @GeneratedValue アノテーションを使用しています。
リスト 1 では name というフィールドも宣言しています。チームの名前となるこのフィールドには、JPA アノテーションを使用していないことに注目してください。デフォルトでは、フィールドは同じ名前の列にマッピングされるので、この記事の目的からすると、それで十分です。最後に、各チームには複数のプレイヤーが関連付けられることになるため、@OneToMany アノテーションを使用して、チームとプレイヤーが 1 対多の関係で管理されるということを JPA ランタイムに示しています。Java クラスでは、この関係は Player オブジェクトの java.util.Collection にすぎません。リスト 2 に参照先となっている Player クラスを示します。
リスト 2.
Player データ・モデル・クラス
@Entity
public class Player {
....
....@Id
....@GeneratedValue(strategy = GenerationType.IDENTITY)
....private long id;
....
....private String firstName;
....
....private String lastName;
....
....private int age;
....
....@ManyToOne (cascade=CascadeType.ALL)
....private Team team;
....
// getters and setters
}
|
リスト 2 に記載する Player クラスはリスト 1 の Team クラスと同様です。このクラスのほうがフィールドの数は多くなっていますが、それでもやはり、大抵の場合はフィールドに付けるアノテーションについて心配する必要はありません。JPA が代わりに正しく処理してくれるからです。リスト 1 とリスト 2 で異なる唯一の点は、Team クラスに対する Player クラスの関係を指定する方法です。1 つの Team には複数の Player が関連付けられるため、このクラスの場合には @ManyToOne アノテーションを使用します。カスケード・ポリシーを指定している点にも注目してください。それぞれのアプリケーションに適切なカスケード・ポリシーを選ぶには、JPA の資料を調べる必要があります。この例で指定しているポリシーでは、新しい Team と Player を同時に作成すれば JPA がその両方を保存してくれるので、このアプリケーションには好都合です。
以上で 2 つのクラスは宣言できたので、他に必要な作業は JPA ランタイムにデータベースへの接続方法を指示することだけです。それには、persistence.xml ファイルを作成します。JPA ランタイムはこのファイルを見つけて、そこに含まれるメタデータを使用しなければなりません。そのための最も簡単な方法として、ソース・コードのサブディレクトリーである /META-INF ディレクトリーにこのファイルを配置します (コンパイルしたクラスが出力されるディレクトリーのルートにファイルがありさえすればよいのです)。リスト 3 に persistence.xml ファイルの内容を記載します。
リスト 3. サッカー・アプリケーションの persistence.xml
<persistence version="1.0"
....xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
....xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
....<persistence-unit name="soccer">
........<class>org.developerworks.soccer.model.Team</class>
........<class>org.developerworks.soccer.model.Player</class>
........<properties>
............<property name="hibernate.dialect"
value="org.hibernate.dialect.DerbyDialect" />
............<property name="hibernate.connection.driver_class"....
value="org.apache.derby.jdbc.EmbeddedDriver" />
............<property name="hibernate.connection.url"
value="jdbc:derby:soccerorgdb;create=true" />
............<property name="hibernate.hbm2ddl.auto" value="update" />
............<property name="hibernate.show_sql" value="true" />
............<property name="hibernate.connection.characterEncoding"
value="UTF-8" />
............<property name="hibernate.connection.useUnicode"
value="true" />
........</properties>
....</persistence-unit>
</persistence>
|
リスト 1 とリスト 2 をもう一度見てみると、コード全体が JPA の標準的なコードで組み立てられています。実際のところ、使用しているのは JPA アノテーションといくつかの定数だけです。データベースや、使用している JPA 実装に固有なものは何もありません。リスト 3 を見るとわかるように、データベースおよび実装に固有の内容は persistence.xml ファイルにあります。優れた JPA 実装には OpenJPA や TopLink (「参考文献」を参照) などがありますが、ここでは由緒ある Hibernate を使用したため、このファイルには Hibernate 固有のプロパティーがいくつか指定されています。これらのプロパティーで指定されているのは、主に JDBC ドライバーや URL のように単純なものであり、Hibernate に対し、実行中の SQL をログに記録するように指示する (本番環境ではもちろん行わないことですが、開発中にデバッグするにはもってこいです) などといった便利なプロパティーもあります。
リスト 3 からは、Apache Derby データベースを使用していることもわかります。実際、ここではこのデータベースの組み込みバージョンを使用しているので、データベースを別途起動する必要も、構成について心配する必要もありません。さらに、接続 URL にはデータベースが自動的に作成する URL を指定し、Hibernate にスキーマを自動的に作成するように指示しています (hibernate.hbm2ddl.auto プロパティー)。したがって、アプリケーションを実行するだけで、データベースとテーブルがすべて自動的に作成されるというわけです。これは開発の際には最適な方法ですが、本番システムには当然、別の設定が必要になります。以上で、データ・モデル・コードをすべて作成し、JPA によるアクセスができるようにしました。次は、Web アプリケーションがこのデータを利用できるように、データベースのデータを公開する方法を説明します。
このアプリケーションを 5 年前に作成しているとしたら、ここで、JSP (Java Server Pages) や JSF (Java Server Faces)、あるいは同様のテンプレート技術の作成に取り掛かるところですが、このアプリケーションでは UI をサーバー上で作成する代わりに、Dojo を使ってクライアントで UI を作成します。必要な作業は、クライアント・サイドのコードが Ajax を使用してデータにアクセスするための手段を提供することだけです。このような場合にはテンプレート・ソリューションを使用することもできますが、それよりも JAX-RS (Java API for RESTful Web Services) を使ったほうが遥かに簡単です。まずは、データベースに保管されたすべての Team を読み取り、新しい Team を作成するためのクラスを作成するところから取り掛かります。リスト 4 に、このようなクラスの一例を記載します。
リスト 4.
Team のデータ・アクセス・クラス
@Path("/teams")
public class TeamDao {
....
....private EntityManager mgr =
DaoHelper.getInstance().getEntityManager();
....
....@GET
....@Produces("application/json")....
....public Collection<Team> getAll(){
........TypedQuery<Team> query =
mgr.createQuery("SELECT t FROM Team t", Team.class);
........return query.getResultList();
....}
....
....@POST
....@Consumes("application/x-www-form-urlencoded")
....@Produces("application/json")
....public Team createTeam(@FormParam("teamName") String teamName){
........Team team = new Team();
........team.setName(teamName);
........EntityTransaction txn = mgr.getTransaction();
........txn.begin();
........mgr.persist(team);
........txn.commit();
........return team;
....}
}
|
リスト 4 に示されているのはクラスのデータ・アクセス・オブジェクト・クラスであるため、その名前は TeamDao となっています。このクラスのアノテーションについてはこの後すぐに説明するとして、まずはデータ・アクセスについて説明します。上記のクラスでは、EntityManager という JPA クラスを参照しています。この EntityManager は JPA の中心的なクラスであり、データベースへアクセスする手段を提供します。リーグに参加するすべてのチームを取得する最初のメソッドでは、EntityManager を使用してクエリーを作成します。クエリーは、SQL と非常によく似た JPA の問い合わせ言語を使用します。このクエリーは単に、すべての Team を取得するだけです。2 番目のメソッドでは、渡されたチームの名前を使用して新しい Team を作成し、トランザクションを作成して新規チームを保存してから、EntityManager を使用してトランザクションをコミットします。このコード全体はありきたりな JPA コードで、これらのクラスとインターフェースのすべては、基本 API に含まれています。
リスト 4 の JPA の部分を理解したところで、次はこのコードが持つ JAX-RS の側面について話を進めます。最初に気付く点は、このクラスを HTTP ベースのクライアントに公開するために、@Path アノテーションを使用していることです。/teams ストリングが指定するのはクラスの相対パスで、完全な URL パスは <host>/SoccerOrg/resources/teams となります。この URL を構成する /SoccerOr の部分は Web アプリケーションのパスを指定します (この部分はもちろん、別のアプリケーションを指すように構成することも、完全に削除することもできます)。/resources の部分は、JAX-RS エンドポイントを指定するために使用されます。そして最後の /teams が @Path アノテーションに対応する部分で、JAX-RS クラスのどれを使用するかを指定します。
次に、このクラスの 1 番目のメソッドである getAll には @GET アノテーションが設定されています。このアノテーションは、HTTP の GET リクエストを受け取った場合に、geAll メソッドを呼び出すことを指定します。続いてこのメソッドに設定されている @Produces は、レスポンスの MIME タイプを宣言するアノテーションです。このアプリケーションでは、JavaScript ベースのクライアントで使うには最も簡単であるという理由から、JSON レスポンスを生成します。
JAX-RS を使用してこのデータ・アクセス・クラスを Web クライアントに公開するために必要なことは、以上ですべてです。しかし皆さんは、このメソッドが Team オブジェクトの java.util.Collection を返した場合、このクラスはどのように Web クライアントに送信されるのだろうかと疑問に思っていることでしょう。@Produces アノテーションはクラスを JSON として送信するように宣言していますが、JAX-RS はどのように JSON にシリアライズするのでしょうか。JSON へのシリアライズを可能にするには、アノテーションをもう 1 つ、Team クラスに追加するだけでよいのです (リスト 5 を参照)。
リスト 5. 変更後の
Team クラス
@XmlRootElement
@Entity
public class Team {
....
// unchanged from Listing 1
........
}
|
@XmlRootElement アノテーションを追加することによって、JAX-RS はこのクラスを JSON オブジェクトに変換できるようになります。皆さんは、このアノテーションには見覚えがあることでしょう。これは JAX-RS に含まれるアノテーションではなく、コアの Java 1.6 プラットフォームの一部となっているJAXB (Java Architecture for XML Binding) API に含まれるアノテーションです。このアノテーションはその名前から XML 専用であるように思えるかもしれませんが、実のところ、JSON を含むさまざまな JAXB 出力に使用することができます。JAXB アノテーションはこの他にも多数ありますが、サンプル・アプリケーションで使用しなければならないのは、このアノテーションだけです。このアノテーションは規約に従って単純に Team クラスのすべてのフィールドを JSON にシリアライズします。
ここでリスト 4 に戻って、クラスの 2 番目のメソッドである createTeam メソッドを見てください。このメソッドでは、HTTP の POST リクエストの受信時に呼び出されるメソッドであることを指定するために、@POST アノテーションを使用しています。続いて @Consumes アノテーションによって、使用可能な POST リクエストの種類を指定しています。このアノテーションで指定される値は、HTTP リクエストの content-type ヘッダーと一致します。上記の場合は、x-www-form-urlencoded として指定されていますが、これは HTML フォームが送信されると受け取るタイプです。したがってこのメソッドは、/SoccerOrg/resources/teams エンドポイントで HTML フォームが送信された場合に呼び出されることになります。最後に注目する点は、このメソッドが引数として取る唯一の入力パラメーターです。これは teamName という名前のストリングで、@FormParam アノテーションで修飾されています。このアノテーションは、JAX-RS ランタイムに対し、teamName (アノテーションの値) という名前のリクエストの本体でフォーム・パラメーターを検索し、そのパラメーターをメソッドの呼び出し時に渡された変数とバインドするように指示します。これにより、単純なフォームの送信であれば、それを簡単に処理してコードに結び付けられるようになっていますが、送信されるデータが大量にある場合には面倒な事態になります。そのような場合は、これよりも構造化された手法を使用することになります。リスト 6 に、Player オブジェクトを作成する例を記載します。
リスト 6. JAX-RS を使用した構造化
POST データの処理
@Path("/players")
public class PlayerDao {
....private EntityManager mgr =
DaoHelper.getInstance().getEntityManager();
....
....@POST
....@Consumes("application/json")
....@Produces("application/json")
....public Player addPlayer(JAXBElement<Player> player){
........Player p = player.getValue();
........EntityTransaction txn = mgr.getTransaction();
........txn.begin();
........Team t = p.getTeam();
........Team mt = mgr.merge(t);
........p.setTeam(mt);
........mgr.persist(p);
........txn.commit();
........return p;
....}
....
....@GET
....@Produces("application/json")
....public List<Player> getAllPlayers(){
........TypedQuery<Player> query =
............mgr.createQuery("SELECT p FROM Player p", Player.class);
........return query.getResultList();
....}
}
|
リスト 6 の PlayerDao クラスはリスト 5 の TeamDao クラスと非常によく似ています。主な違いは、検討しなければならない対象が addPlayer メソッドであるという点だけです。このメソッドは、TeamDao での createTeam メソッドと同じように HTTP の POST リクエストを処理します。ただし、このメソッドが使用するのは application/json であることから、このメソッドは JSON データを要求しています。ここに含まれる意味は 2 つあります。まず 1 つは、リクエストはこのメソッドが呼び出されるように、content-type に application/json を指定しなければならないこと、そしてもう 1 つは POST リクエストの本体は JSON データでなければならないことです。そこで、このメソッドの入力パラメーターのタイプが JAXBElement<Player> となっていることに注目してください。これはつまり、Player オブジェクトをラップする JAXB ラッパーであるということです。したがって、JAX-RS は POST リクエストで送信されたデータを自動的に JAXBElement ラッパーに構文解析するため、構文解析を行うためのコードを作成する必要はありません。メソッド本体のたった 1 行のコードで完全な Player オブジェクトを取得するため、あとはこのオブジェクトを使って、JPA で新規 Player をデータベースに保存することができます。
JAX-RS の説明を締めくくるには、最後にすべてを 1 つに組み立てるために必要な構成を明らかにしなければなりません。すべてを組み立てるために必要なことは、アプリケーションの web.xml を変更することだけです。リスト 7 に、このサンプル・アプリケーションの web.xml を記載します。
リスト 7. アプリケーションの web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
....xmlns="http://java.sun.com/xml/ns/javaee"
....xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
....xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
....id="Soccer_Org" version="2.5">
<display-name>SoccerOrg</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>JAXRS-Servlet</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.
ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>org.developerworks.soccer.model;org.developerworks.
soccer.web</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>JAXRS-Servlet</servlet-name>
<url-pattern>/resources/*</url-pattern>
</servlet-mapping>
</web-app>
|
リスト 7 を見るとわかるように、このアプリケーションでは 1 つのサーブレットしか宣言されていません。このサーブレットは、このアプリケーションで JAX-RS 実装として使用している Jersey に用意されているもので、このサーブレットには初期化パラメーターを 1 つだけ渡しています。パラメーターとして渡しているのは、JAX-RS に認識させるすべてのクラスが含まれるパッケージですが、サンプル・アプリケーションの場合、データ・モデルが保持されるパッケージと、データ・アクセス・オブジェクトが保持されるパッケージがあります。また、JAX-RS がデータ・モデルを JSON に変換できるように、データ・モデルを検出可能な状態にしておく必要があります。DAO についても当然のことながら検出可能にして、JAX-RS がリクエストを DAO にルーティングできるようにしておかなければなりません。最後に、servlet-mapping に着目してください。ここに、URL パスの一部として含まれる /resources の部分が指定されます。これで、クライアントでこのすべてのバックエンド・コードと Dojo を使用して UI を作成する準備が整いました。
Dojo ツールキットには、Web アプリケーションのクライアント・サイドを構築するために必要となるようなライブラリーやユーティリティーがほとんどすべて用意されています。このツールキットがいかに役立つかは、Ajax、フォーム、JSON を操作して UI ウィジェットを作成してみるとわかるはずです (Dojo ツールキットには、これ以外にも遥かに多くの機能がありますが、この単純なサンプル・アプリケーションでは、UI ウィジェットの作成だけが唯一必要な作業であるというだけです)。Dojo ツールキットはかなり大きなシステムなので、ツールキット一式をダウンロードした後、アプリケーションに必要な機能だけを使用できるようにカスタム・ビルドを行うのでも構いません。このサンプル・アプリケーションでは、カスタム・ビルドを行う代わりに Google Ajax API を使用して、必要とするツールキットの各種部分にアクセスします。この方法は便利であるというだけでなく、Google の Dojo のコピーは Google 独自の極めて効率的なコンテンツ配信ネットワーク (CDN) から提供されるため、パフォーマンス面でもメリットがあります。
このサンプル・アプリケーションはデータ中心のアプリケーションなので、何はともあれ、アプリケーションにデータを追加しなければなりません。Team を追加するための UI は、Dojo を使用して作成します。リスト 8 に、そのために必要なコード一式を記載します。
リスト 8. Dojo を使用した
Team の追加
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Test Harness</title>
<link rel="stylesheet" type="text/css"
....href="http://ajax.googleapis.com/ajax/libs/dojo/1.4/dijit
/themes/soria/soria.css"/>
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/dojo/1.4/dojo/dojo.xd.js"
djConfig="parseOnLoad: true"></script>
<script type="text/javascript">
....function init(){
.... var btn = dijit.byId("addTeamBtn");
.... dojo.connect(btn, "onClick", function(event){
.... ....event.preventDefault();
event.stopPropagation();
dojo.xhrPost({
form : dojo.byId("addTeamForm"),
....handleAs: "json",
....load : function(data){
........addTeam(data);
........alert("Team added");
....},
....error : function(error){
.... alert("Error adding team: " + error);
....}
});
.... });
....}
</script>
</head>
<body class="soria">
....Add a Team<br/>
....<form method="POST" action="/SoccerOrg/resources/teams" id="addTeamForm">
........<label for="teamName">Team Name:</label>
........<input name="teamName" type="text" id="teamName"
dojoType="dijit.form.TextBox"/>
........<button type="submit" id="addTeamBtn" dojoType=
"dijit.form.Button">Add Team</button>
....</form>
....<script type="text/javascript">
........dojo.require("dijit.form.Button");
.... dojo.require("dijit.form.TextBox");
.... dojo.addOnLoad(init);
....</script>
</body>
</html>
|
リスト 8 では、Google の CDN から基本 Dojo ライブラリーを参照しています。Dojo を参照した後は、dojo.require 関数を使用して、追加で必要な Dojo の各部分をリクエストすることができます (リスト 8 の最後にある script ブロックを参照)。作成している HTML フォームは標準的なものですが、さらに追加で Dojo 固有の属性を使用していることに注目してください。Dojo 固有の属性により、Dojo に対し、視覚要素にスタイルを追加し、対応する DOM 要素に機能を追加するように指示します。そして他のすべて (すべての Dojo コンポーネント) がロードされた後、Dojo に init 関数を実行させます。この関数の中では、フォーム内のボタンのハンドルを取得するために dijit.byId 関数を使用します。Dijit は、Dojo のウィジェット・ライブラリーです。dojo.byId を使用すれば、あらゆる DOM 要素をその ID によって参照できますが、これと同様の dijit.byId は、さらに多くの機能を持つウィジェットを実現します (これが実現されるのは、要素にウィジェットとしてのマークが付けられた場合であり、リスト 8 でこれに該当するのはボタンです)。
続いて Dojo を使用して、ボタンのクリックに対応するイベント・ハンドラーを関連付けます。ハンドラーはフォームの送信を停止し、代わりに Ajax を使用するために dojo.xhrPost 関数を使用します。HTML フォームの POST を容易にするこの関数は、HTML フォームの action 属性を検査して Ajax エンドポイントを突き止めます。さらに、すべてのフォーム要素を読み取ってAjax POST に渡します。サーバーからのレスポンスを受け取ると、load 関数を呼び出し、この関数が xhrPost に渡されます。注意する点として、上記では xhrPost 関数に渡される handleAs プロパティーを設定して、サーバーが JSON を返すことを宣言しています。この後 addTeam 関数について説明しますが、Dojo はすでに安全に、JSON データを有効な JavaScript オブジェクトに構文解析していることから、データ・オブジェクトを直接渡すことができます。この addTeam 関数は、Player を追加するための別のフォームと併せて使用されます。リスト 9 に、そのフォームの HTML を記載します。
リスト 9.
Player 追加用フォーム
Add a Player<br/>
<form id="addPlayerForm" action="/SoccerOrg/resources/players">
....<label for="firstName">First Name:</label>
....<input name="firstName" id="firstName" type="text"
dojoType="dijit.form.TextBox"/>
....<label for="lastName">Last Name:</label>
....<input type="text" name="lastName" id="lastName"
dojoType="dijit.form.TextBox"/><br/>
....<label for="age">Age:</label>
....<input type="text" name="age" id="age"
dojoType="dijit.form.TextBox"/><br/>
....<label for="team">Team:</label>
....<select id="team" name="team" dojoType="dijit.form.
Select"></select>
....<button type="submit" id="addPlayerBtn" dojoType=
"dijit.form.Button">Add Player</button>
</form>
<script type="text/javascript">
dojo.require("dijit.form.Select");
dojo.addOnLoad(loadTeams);
</script>
|
上記のフォームは、リスト 8 に記載したフォームと同様の有効な HTML フォームですが、このフォームの要素には Dojo 固有の属性が追加されています。このフォームにある select 要素は Team のドロップダウン・リストとして機能し、ユーザーが新しい Player を追加する Team を選択できるようになっています。これは動的データなので、サーバーからロードしなければなりません。そのために追加されているのが、起動時に呼び出される loadTeams 関数です。この関数が、サーバーからチームをロードします。リスト 10 にこの関数と併せ、リスト 9 で参照した addTeam 関数を記載します。
リスト 10.
loadTeams 関数と addTeam 関数
var teams = {};
function loadTeams(){
....var select = dijit.byId("team");
....dojo.xhrGet({
........url: "/SoccerOrg/resources/teams",
........handleAs:"json",
........load : function(data){
............var i = 0;
............for (i in data.team){
................addTeam(data.team[i]);
............}
........},
........error : function(error){
............alert("Error loading team data: " + error);
........}
....});
}
function addTeam(team){
....teams[team.id] = team;
....var select = dijit.byId("team");
....var opt = {"label":team.name, "value":team.id};
....select.addOption(opt);
}
|
上記でも、前に作成した JAX-RS エンドポイントが提供するデータにアクセスするために Dojo の Ajax ユーティリティーを使用しています。今回使用している dojo.xhrGet は、Ajax エンドポイントに対して HTTP の GET リクエストを行います。このアプリケーションでは Ajax エンドポイントの URL を指定する必要がありますが、その点を除けば、リスト 9 に記載されている xhrPost とほとんど変わりません。最後に、addTeam メソッドに目を移すと、ここでもやはり Dojo ウィジェットの追加機能を使用して、チームを表示するドロップダウン・リストに新しいオプションを簡単に追加できるようにしています。プレイヤー用のフォームを作成する方法を説明したところで、このフォームの送信を処理するコードを見てください (リスト 11 を参照)。
リスト 11. 新規
Player の追加
var button = dijit.byId("addPlayerBtn");
dojo.connect(button, "onClick", function(event){
.... event.preventDefault();
event.stopPropagation();
var data = dojo.formToObject("addPlayerForm");
var team = teams[data.team];
data.team = team;
data = dojo.toJson(data);
var xhrArgs = {
postData: data,
handleAs: "json",
load: function(data) {
alert("Player added: " + data);
dojo.byId("gridContainer").innerHTML = ";
loadPlayers();
},
error: function(error) {
alert("Error! " + error);
},
url: "/SoccerOrg/resources/players",
headers: { "Content-Type": "application/json"}
};
var deferred = dojo.xhrPost(xhrArgs);
});
|
上記のコードが、リスト 6 に示されていた PlayerDao.addPlayer メソッドにデータを送信することになります。このコードは、Player オブジェクトが JSON データ構造にシリアライズされることを要求します。コードではまず、再び Dojo を使用して、イベント・ハンドラーをフォームでのボタンのクリックに関連付けます。次に、Dojo のコンビニエンス関数 dojo.formToObject によってフォームからのデータのすべてを JavaScript オブジェクトに変換します。その JavaScript オブジェクトを多少変更し、サーバーで要求される構造と一致させた後、Dojo の dojo.toJson 関数を使用して JSON ストリングに変換します。そして、addTeam フォームが送信された方法と同じような方法で、JSON ストリングが dojo.xhrPost に渡されます。最後に、HTTP ヘッダーの Content-Type を追加して、確実にPlayerDao.addPlayer メソッドにルーティングされるようにしている点に注意してください。
xhrPost にも load 関数があり、Ajax リクエストに対してサーバーから成功のレスポンスが返ってくると、この関数が呼び出されます。すると、この関数がページ上の gridContainer という要素をクリアし、loadPlayers という関数を呼び出します。これもまた Dojo ウィジェットであり、すべてのプレイヤーを表示するために使用されます。リスト 12 に、このウィジェットに使用する HTML と JavaScript を記載します。
リスト 12. プレイヤー・グリッドの HTML と JavaScript
<style type="text/css">
@import
"http://ajax.googleapis.com/ajax/libs/dojo/1.4/dojox/grid/resources/Grid.css";
@import
"http://ajax.googleapis.com/ajax/libs/dojo/1.4/dojox/grid/resources/soriaGrid.css";
.dojoxGrid table { margin: 0; }
html, body { width: 100%; height: 100%; margin: 0; }
</style>
<script type="text/javascript">
function loadPlayers(){
....var pStore = new dojox.data.JsonRestStore({
........target: "/SoccerOrg/resources/players"
....});
....pStore._processResults = function(data, deferred){
........return {totalCount:deferred.fullLength || data.player.length,
items: data.player};
....};
var pLayout = [{
field: "firstName",
name: "First Name",
width: "200px"
},
{
field: "lastName",
name: "Last Name",
width: "200px"
},
{
field: "age",
name: "Age",
width: "100px"
},
{
field : "teamName",
name : "Team",
width: "200px"
}];
var grid = new dojox.grid.DataGrid({
store: pStore,
clientSort: true,
rowSelector: "20px",
structure: pLayout
}, document.createElement("div"));
dojo.byId("gridContainer").appendChild(grid.domNode);
grid.startup();
}
</script>
<div id="gridContainer" style="width: 100%; height: 100%;"></div>
<script type="text/javascript">
dojo.require("dojox.grid.DataGrid");
dojo.require("dojox.data.JsonRestStore");
dojo.addOnLoad(loadPlayers);
</script>
|
リスト 12 に記載されている Dojo の DataGrid ウィジェットは、Dojo ではとりわけリッチなウィジェットの 1 つなので、追加の CSS も必要になってきます。グリッドを作成するためには、2 つの作業を行わなければなりません。1 つは、グリッド用のデータ・ストアを作成することです。このアプリケーションの場合、サーバーから送信されてくるのは JSON データであるため、新しい JsonRestStore オブジェクトを作成し、そこに、このデータを生成するサーバー上の URL を指定します。その上で、オブジェクトの _processResults をオーバーライドします。これをオーバーライドしなければならない理由は単に、このオブジェクトが要求するのはデータの JSON 配列ですが、JAX-RS エンドポイントはそれよりも多少複雑なオブジェクトを生成するためです (このオブジェクトには player という単一のプロパティーがあり、その値は、JsonRestStore が要求する JSON 配列となります)。次にグリッドに必要なものは、表示する列と、JavaScript オブジェクトでそれに対応するプロパティーを指示するレイアウト・メタデータです。この 2 つの作業が完了すれば、グリッドを作成して DOM ツリーにドロップすることができます。
サンプル・サッカー・アプリケーションは完成し、リーグに参加するサッカー・プレイヤーを極めてリッチな方法で表示できるようになりました。この単純なサンプル・アプリケーションをここから拡張するのは簡単です。プレイヤーの編集機能やグリッドのソート機能を追加したり、試合と結果などのデータを追加したりしてください。
この記事では、データ中心のリッチな Web アプリケーションを簡単に作成する方法を紹介しました。厄介なボイラープレート・コードをサーバー・サイドとクライアント・サイドの両方から取り除くために使った重要な技術は、JPA、JAX-RS、そして Dojo です。多くの場合はデフォルトの規約を利用することによって、Web アプリケーションを作成するために必要なコードの量をさらに減らしました。その結果、最小限のコードで作成された非常に現代的なアプリケーションが完成しました。アプリケーションで使用した技術はすべて、拡張性と本番に即した品質を併せ持っているため、このサンプル・アプリケーション (あるいは独自のアプリケーション) をより堅牢な使用ケースに対応できるように簡単に拡張することができます。さらに良いことに、このアプリケーションには何の束縛もありません。サーバー・サイドではオープン・スタンダードを使用したので、例えばデータベース技術を簡単に切り替えることができます。フロントエンドでは REST と JSON を使用しました。これはつまり、別の UI キットを使用することも、モバイル・クライアントに簡単に接続することもできるということです。
| 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|---|---|---|
| Article source code | SoccerOrg.zip | 14KB | HTTP |
学ぶために
- 「Introduction to Spring 2 and JPA」(Sing Li 著、developerWorks、2006年8月): JPA について詳しく学んでください。
- 「Implementing composite keys with JPA and Hibernate」(Stephen Morris 著、developerWorks、2009年8月): JPA と Hibernate の詳細を探ってください。
- 「RESTful な Web サービスを Java 技術で作成する」(Dustin Amrhein、Nick Gallardo 共著、developerWorks、2010年2月): JAX-RS の全容を紹介している記事です。
- 「モバイル Web 用の Ajax アプリケーションを作成する」(Michael Galpin 著、developerWorks、2010年3月):JAX-RS はモバイル Web アプリケーションにも最適である理由を説明しています。
- 「カスタム Dojo アプリケーションを作成する」(Wendi Nusbickel、Melissa Betancourt 共著、developerWorks、2008年12月): Dojo について詳しく説明しています。
- 「Develop HTML widgets with Dojo」(Igor Kusakov 著、developerWorks、2006年10月): Dojo の拡張性を探ってください。
- 「Comment lines: Using Ant and ShrinkSafe to improve performance of Web applications that use Dojo」(Kevin Haverlock 著、developerWorks、2010年3月): REST 呼び出しを使った JSON データの遅延ロードで Dojo Dijit ツリー・ウィジェットにデータを取り込む実際の例を紹介しています。
- 「コメント行: Scott Johnson: Dojo Dijit ツリー・ウィジェットを遅延ロードすることによりパフォーマンスを改善する」(Scott Johnson 著、developerWorks、2008年5月): Ant ビルド・ユーティリティーと Dojo の ShrinkSafe を使用してプロファイルのビルドを自動化することにより、Dojo ベースの Web ページのパフォーマンスが改善される仕組みを学んでください。
- developerWorks Web development ゾーンでは、多種多様な Web ベースのソリューションを取り上げた記事を揃えています。
製品や技術を入手するために
- Dojo ツールキットをダウンロードしてください。
- Java SDK を入手してください。この記事では JDK 1.6.0_17 を使用しました。
- Apache Tomcat を入手してください。この記事では Apache Tomcat 6.0.14 を使用しました。
- Apache Derby 10.6.1.0 を入手してください。
- Jersey は実動に対応する品質を備えた、オープンソースの JAX-RS リファレンス実装です。
- Hibernate は JPA (Java Persistence API) の実装です。この記事ではバージョン 3.5.3 を使用しました。
- IBM 製品の評価版をダウンロードして、DB2、Lotus、Rational、Tivoli、および WebSphere のアプリケーション開発ツールとミドルウェア製品を使ってみてください。
議論するために
- 今すぐ My developerWorks で自分のプロフィールを作って、Dojo に関するウォッチ・リストをセットアップしてください。My developerWorks とずっとつながっていられます。
- Web 開発に興味を持つ他の developerWorks メンバーを見つけてください。
- Web Development グループで、Web 開発者としての経験と知識を共有してください。
- Web のトピックを専門とする developerWorks グループに参加して、知識を共有してください。
- Roland Barcia が彼のブログで Web 2.0 とミドルウェアについて語っています。
- developerWorks のメンバーが共有する Web のトピックに関するブックマークをフォローしてください。
- Web 2.0 Apps フォーラムで、素早く回答を得てください。

Michael Galpin は eBay のアーキテクトであり、developerWorks に頻繁に寄稿しています。彼は JavaOne、EclipseCon、AjaxWorld など、さまざまな技術カンファレンスで講演を行っています。彼が次に取り組もうとしていることを知るには、Twitter で @michaelg をフォローしてください。