本文へジャンプ

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

送信されたすべての情報は安全です。

  • 閉じる [x]

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


送信されたすべての情報は安全です。

  • 閉じる [x]

JAX-RS、JPA、Dojo を使ってデータ中心のリッチな Web アプリケーションを作成する

青少年サッカー・リーグの管理用アプリケーションを開発する

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

概要: 大量のデータを操作するリッチ・アプリケーションの開発は、これまでデスクトップ・アプリケーションの独占領域でした。けれども今では Web アプリケーションでも開発できるようになり、Java™Script の第一人者でなくても、リッチ・アプリケーションを開発することができます。この記事を読んで、Dojo ツールキットを使って目を見張るようなデータ中心の Web アプリケーションを作成し、JAX-RS や JPA などの JavaEE 標準をベースとしたバックエンドに接続する方法を学んでください。これらの技術を使用することで、「Convention over Configuration (設定より規約)」の原則を利用して、複数の複雑なアプリケーションをあっという間に 1 つに組み立てることができます。

日付:  2010年 7月 13日
レベル:  中級 この記事の原文:  英語
アクティビティー: 9259 ビュー
お気軽にご意見・ご感想をお寄せください: 


はじめに

この記事では、最新のサーバー・サイドの 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 クラスはリスト 1Team クラスと同様です。このクラスのほうがフィールドの数は多くなっていますが、それでもやはり、大抵の場合はフィールドに付けるアノテーションについて心配する必要はありません。JPA が代わりに正しく処理してくれるからです。リスト 1リスト 2 で異なる唯一の点は、Team クラスに対する Player クラスの関係を指定する方法です。1 つの Team には複数の Player が関連付けられるため、このクラスの場合には @ManyToOne アノテーションを使用します。カスケード・ポリシーを指定している点にも注目してください。それぞれのアプリケーションに適切なカスケード・ポリシーを選ぶには、JPA の資料を調べる必要があります。この例で指定しているポリシーでは、新しい TeamPlayer を同時に作成すれば 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 アプリケーションがこのデータを利用できるように、データベースのデータを公開する方法を説明します。


JAX-RS による RESTful なデータ・アクセス

このアプリケーションを 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();
....}
}

リスト 6PlayerDao クラスはリスト 5TeamDao クラスと非常によく似ています。主な違いは、検討しなければならない対象が 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 によるクライアントでの REST の利用

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 codeSoccerOrg.zip14KBHTTP

ダウンロード形式について


参考文献

学ぶために

製品や技術を入手するために

  • 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 のアプリケーション開発ツールとミドルウェア製品を使ってみてください。

議論するために

著者について

Michael Galpin's photo

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

不正使用の報告のヘルプ

不正使用の報告

ありがとうございます。 このエントリーは、モデレーターの注目フラグが設定されました。


不正使用の報告のヘルプ

不正使用の報告

不正使用の報告の送信に失敗しました。


developerWorks: サイン・イン


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。 プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。 お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

表示名をお選びください

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

(半角英数字で3文字以上31文字以下にする必要があります)


「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


この記事を評価する

コメント

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Java technology
ArticleID=512987
ArticleTitle=JAX-RS、JPA、Dojo を使ってデータ中心のリッチな Web アプリケーションを作成する
publish-date=07132010
author1-email=mike.sr@gmail.com
author1-email-cc=

タグ

Help
このタグで、My developerWorks のすべてのタイプのコンテンツを見つけるために検索フィールドを使用します。

スライダーバーを使用することで、より多く(少なく)タグを表示します。

人気のタグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するトップのタグを表示します。

マイ・タグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するお客様ご自身のタグを表示します。

このタグで、My developerWorks のすべてのタイプのコンテンツを見つけるために検索フィールドを使用します。人気のタグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するトップのタグを表示します。マイ・タグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するお客様ご自身のタグを表示します。