【第1シリーズ 第1回】 参照型RESTful Webサービスを作る
第1シリーズ RESTful Webサービスとクライアントアプリケーション
こんにちは。ゆめみではPMをしております、hiroといいます。
1. はじめに
HTMLをサーバサイドでオーサリングし、ブラウザはそれを表示するだけという、HTMLベースのWebアプリケーションが一般化して10年くらい経ったのでしょうか。企業の業務アプリケーションや、ショッピングサイト、そして、銀行や市役所などの公共機関の窓口といった、多岐に渡る種類のWebアプリケーションが作られ、Webブラウザさえあればそれらのサービスが利用できるようになり、大変便利になりました。私たちもこれらの仕組みやサービスなしでは業務になりません。
しかし、アプリケーションの機能やインターフェイスに求められるクオリティは増すばかり。近い将来、HTMLやJavaScriptだけでそれらの要求の全てをカバーできなくなるやもしれません。インターネットを介してデータを入出力するデスクトップアプリケーションや、ブラウザ中で稼動するにしても、UIのレンダリングはクライアントコンピュータが担うといった、サーバとクライアントの役割分担が進むでしょう。FlashやSilverlightで実装されるアプリケーションが増えつつあるのは、そのような大きな流れの始まりだと感じています。
このシリーズは、そんな将来を見据え、インターネット空間上に置かれたサーバの役割と、クライアントコンピュータの役割分担をソースコードレベルで見てみようというものです。このシリーズではサーバはJavaによるRESTful Webサービスを実装します。クライアントサイドは、FlashもしくはSilverlightを含む.NETベースのアプリケーションで実装することとします。
2. ダウンロード
なお、このシリーズでは、サーバサイドの開発に、Javaの開発をシンプルにかつ迅速に行えるオールインワンIDEであるNetBeans 6.5を使用します。以下のURLから「すべて」をダウンロードしてお使いください。
http://www.netbeans.org/downloads/index.html
また、データベースには、MySQL 5.1のコミュニティ版を使います。以下のURLからダウンロードしてお使いください。
http://dev.mysql.com/downloads/mysql/5.1.html
NetBeans IDEも、MySQLも、いまや SunMicrosystems社の製品です。NetBeansにはMySQL用のJDBCドライバも付属し、すぐにでも利用できるようになっています。
サーバ側には、標準的なサーブレットコンテナであるTomcatを利用できます。以下のURLからダウンロードしてお使いください。
http://tomcat.apache.org/download-60.cgi
後先になりましたが、JDKは、JDK 6 update 12を使用しました。以下からダウンロードしてください。
http://java.sun.com/javase/downloads/index.jsp
第1回 参照型RESTful Webサービスを作る
1. サービスを定義する
さて、第1回は、まずはデータベース中のデータを、RESTful Webサービスとして参照できるようにします。何を作るか決めましょう。まずはシンプルに、以下の機能を実装しましょう。
● 顧客一覧を表示する
a) 顧客1件は、次の項目から構成される。
・ユニークなID
・名前
・生年月日
・住所
・性別
b) 次のURLで顧客リストにアクセスできる。http://<サーバ:ポート>/Yumewaza1/resource/customer
c) ダウンロードされるのは、XML文字列であり、以下の構造をとる。
<Customers> <customer id="<ユニークなID>"> <name><名前></name> <birthday><生年月日></birthday> <address><住所></address> <sex><性別></sex> </customer> <!-- customer タグが複数出力される --> </Customers>
実にシンプルですね。データベーステーブルの内容をそのままXML文字列にて出力するだけの機能です。
2. プロジェクトの準備
NetBeans IDEのメニューより、「ファイル」→「新規プロジェクト」の順でクリックし、Webアプリケーションを選択してください。このプロジェクトでは、コンテキストパスを「/Yumewaza1」にし、以下のNetBeans 6.5に付属するライブラリを使用しますので追加してください。
(1) JAX-RS 1.0
(2) Jarsey 1.0 (JAX-RS RI)
(3) MySQL JDBC ドライバ
加えて、データベース操作を簡単にするためのライブラリをjarファイルで追加します。
(4) DBTackle.jar
このDBTackle.jarは、私が開発したもので、このブログからダウンロードし、ご利用いただけます。
DBTackle.jarをダウンロード
3. データの準備
まずはデータの準備です。顧客を表すデータベーステーブルを以下のように定義し、初期データを登録しました。
データベース:yumewaza1
CREATE TABLE CUSTOMER ( ID INTEGER, NAME VARCHAR(100), BIRTHDAY DATE, ADDRESS VARCHAR(200), SEX CHAR(1) ); INSERT INTO CUSTOMER (ID, NAME, BIRTHDAY, ADDRESS, SEX) VALUES (1, 'Hiro', '1986-10-10', '神奈川県横浜市青葉区', 'M'); INSERT INTO CUSTOMER (ID, NAME, BIRTHDAY, ADDRESS, SEX) VALUES (2, '夢見 太郎', '2000-01-27', '京都府京都市下京区烏丸通四条下ル水銀屋町620番地', 'M'); INSERT INTO CUSTOMER (ID, NAME, BIRTHDAY, ADDRESS, SEX) VALUES (3, '世田谷 花子', '1932-10-01', '東京都世田谷区', 'F');
テーブルを取得するクエリで、データが存在していることを確かめます。
mysql> select * from customer; +------+-------------+------------+-------------------------------------------------+------+ | id | name | birthday | address | sex | +------+-------------+------------+-------------------------------------------------+------+ | 1 | Hiro | 1986-10-10 | 神奈川県横浜市青葉区 | M | | 2 | 夢見 太郎 | 2000-01-27 | 京都府京都市下京区烏丸通四条下ル水銀屋町620番地 | M | | 3 | 世田谷 花子 | 1932-10-01 | 東京都世田谷区 | F | +------+-------------+------------+-------------------------------------------------+------+ 3 rows in set (0.00 sec)
4. web.xmlの準備
次に、web.xml に以下のタグを追加します。これは、いわゆるServletとURLのマッピングの設定を追加しています。Servletのクラス名から見てもわかるように、JAX-RSの実装ライブラリであるJerseyのServletクラスが、パス/resource以下のURLを全て処理するように設定されます。これは、RESTful Webサービスが、実際はServletコンテナ上で稼動することを意味します。
<servlet> <servlet-name>ServletAdaptor</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>ServletAdaptor</servlet-name> <url-pattern>/resource/*</url-pattern> </servlet-mapping>
5. プログラミング
お待たせしました!今までは設定と準備、これからはプログラミングです。機能を追加する場合、この単位を横展開すればよいということになります。以下のクラスを作成します。
(1) 顧客1件を表すクラス:test.Customer
(2) 性別をJava言語上の列挙子として扱うための列挙子クラス:test.Sex
(3) DBTackleのデータベース処理ラッパー実装クラス:test.DatabaseImpl
(4) リソースに依存する処理を実装するクラス:test.CustomerResource
(1)は、Customerクラスにて顧客1件分のデータを表します。まずは以下のソースコードをご覧ください。
1: package test; 2: 3: import java.util.*; 4: import javax.xml.bind.annotation.*; 5: 6: import dbtackle.annotation.Column; 7: 8: @XmlRootElement(name = "customer") 9: public class Customer { 10: @XmlAttribute 11: @Column("ID") 12: public int id; 13: 14: @Column("NAME") 15: public String name; 16: 17: @Column("BIRTHDAY") 18: public Date birthday; 19: 20: @Column("ADDRESS") 21: public String address; 22: 23: @Column(value = "SEX", enumType = Sex.class) 24: public Sex sex; 25: }
このクラスの役割は、データベースからオブジェクトへのマッピングと、オブジェクトからXMLへのバインディングの仲介をするということです。データベース操作用のライブラリであるDBTackleも、XMLバインディング用のライブラリであるJAXBも、アノテーションによるマッピング・バインディングをサポートしています。従って、このクラスのフィールドに必要なアノテーションをマーキングするだけです。メソッドの実装は必要ありません。
アノテーションの説明を簡単にしておきましょう。まず、DBTackleでのアノテーション@Column に関して。これは、SELECT文で取得された結果セットから、カラム名を指定して値を取得する際に必要な名称を指定し、フィールドにマッピングするようにするものです。たとえば、SELECT COUNT(*) AS CNT FROM TABLE_X とした場合、1カラム目は CNT という名称でアクセスできるため、@Column("CNT") とすることで、オブジェクトにマップされます。
SEXカラムのマッピングの記述が他のカラムと違うのは、テーブル上でCHAR型であるものをJava言語の列挙子にマッピングするためです。enumType という拡張で、列挙子のクラスオブジェクトを指定します。
もうひとつ、アノテーションの種類があります。8行目の@XmlRootElementおよび10行目の@XmlAttributeです。これは、JAXBで使用するアノテーションで、これらを指定することで、オブジェクトを自動的にXML文字列に変換してくれます。@XmlRootElementは、このクラスがXML上のルート要素になりうることを示します。クラスがタグとなり、<customer>となります。フィールドはその入れ子のタグとなり、フィールド名がそのまま<name>や<address>などのタグとなります。@XmlAttributeは、そのクラスがバインディングされるタグの属性となります。この場合、id フィールドは、@XmlAttribute属性が付与されているので、入れ子のタグとはならず、<customer>タグの属性となり、<customer id="xxxx">のように出力されることになります。
次に、(2) Sex列挙子の定義です。以下のソースコードをご覧ください。
1: package test; 2: 3: import dbtackle.annotation.Mnemonic; 4: 5: public enum Sex { 6: @Mnemonic("M") 7: MALE, 8: 9: @Mnemonic("F") 10: FEMALE, 11: 12: @Mnemonic("U") 13: UNKNOWN, 14: }
各フィールドに@Mnemonicアノテーションが指定してあります。これによって、テーブル上のSEXカラムに入っているCHAR型の1文字に対し、Sex列挙子のどのフィールドをマッピングするかが決定されます。なお、この列挙子にはJAXB用のアノテーションが付けられていません。これらの値がXML文字列化されるときは、フィールドの名称がそのままXML文字列になり、「MALE」、「FEMALE」というように出力されます。フィールドの名称以外に文字列化するには、フィールドに@XmlEnumValue("M")のように指定することで可能です。
次に、(3)DBTackleのデータベース処理クラスを実装します。以下のソースコードをご覧ください。
1: package test; 2: 3: import java.sql.*; 4: import dbtackle.Database; 5: 6: public class DatabaseImpl extends Database { 7: static { 8: try { 9: Class.forName("com.mysql.jdbc.Driver"); 10: } 11: catch (ClassNotFoundException ex) { 12: // something to do. 13: } 14: } 15: 16: @Override 17: protected Connection getConnection() throws SQLException { 18: Connection con = DriverManager.getConnection("jdbc:mysql://<MySQLサーバ>/yumewaza1", "<ユーザID>", "<パスワード>"); 19: con.setAutoCommit(false); 20: return con; 21: } 22: }
DBTackleにおけるdbtackle.Databaseクラスは、データベースの接続を表す、java.sql.Connectionオブジェクトのラッパーです。JDBCを直接用いた処理では、ソースコードが長くなる傾向があります。しかし、現実的に用いられるコードの記述パターンは割と少ないと思います。それをカプセル化したのがDBTackleライブラリであり、dbtackle.Databaseクラスです。
dbtackle.Databaseクラスはabstractであり、インスタンスを作成するには必ず継承したクラスを作成する必要があります。この継承先のクラスで行うことはただ1つ、java.sql.Connectionインスタンスを作成することです。この例では16行目から21行目に相当します。dbtackle.Databaseクラスのインスタンス1つにつき、getConnection() メソッドは1回だけコールされます。この例では、古典的にJDBCドライバのインスタンス化と接続を行っています。
最後に、(4)リソースに依存する処理を実装するクラスを作成します。以下のコードをご覧ください。
1: package test; 2: 3: import java.util.*; 4: import java.sql.SQLException; 5: import javax.ws.rs.*; 6: 7: import dbtackle.Database; 8: 9: @Path("customer") 10: public class CustomerResource { 11: @GET 12: @Produces("application/xml") 13: public List<Customer> getCustomerList() { 14: Database database = new DatabaseImpl(); 15: 16: try { 17: return database.select(Customer.class, 18: "SELECT " + 19: " ID, " + 20: " NAME, " + 21: " BIRTHDAY, " + 22: " ADDRESS, " + 23: " SEX " + 24: "FROM " + 25: " CUSTOMER "); 26: } 27: catch (SQLException ex) { 28: // something to do. 29: return new ArrayList<Customer>(); 30: } 31: finally { 32: database.close(); 33: } 34: } 35: }
まず、JAX-RSでは、リソースへのアクセスをメソッドのコールで表現しています。そして、リソースのURIとクラスおよびメソッドをマッピングするためにアノテーションを持ちます。冒頭で、顧客リストへのアクセスのためのURLを提示しました。
http://<サーバ:ポート>/Yumewaza1/resource/customer
このうち、<サーバ:ポート>以下の部分を順に解説すると、まず、「Yumewaza1」はこのWebアプリケーションのコンテキストルートとなります。次に、「resource」は、ServletとURLのマッピングの際に指定し、これ以下のURLがRESTful Webサービスの対象となることを宣言しました。つまり、「customer」がリソースということになります。
RESTful Webサービスでは、リソース名に対し、実際どのようなリソースを割り当てるかは開発者に委ねられています。開発段階で、リソースのURIにどのような意味を持たせるか、はっきりしておかなければなりません。ここでは/customerを顧客全体の集合として表現することとしました。
さて、9行目に見られるように、このCustomerResourceクラスに付与されている@Pathアノテーションの値がcustomerになっています。これによって、このクラスは /Yumewaza1/resource/customer 以下を扱えることを示しています。次に、11行目に@GETとあります。これは、このリソースに対し、HTTPのGETメソッドでアクセスされた場合の処理であることを示し、メソッド getCustomerList()にマップしています。つまり、http://<サーバ:ポート>/Yumewaza1/resource/customer を、GETメソッドでアクセスすると、getCustomerList()がコールされる、ということです。
次に、12行目に見られる@Producesアノテーションです。これは、このメソッドをコールするきっかけとなったHTTPリクエストに対して、HTTPレスポンスを返す際のMIMEタイプを指定します。JAX-RSでは、この指定に基づいて、メソッドから返ったオブジェクトを指定されたコンテンツに変換します。この例では、getCustomerList()は、MIMEタイプがapplication/xmlと指定されていますので、List<Customer>オブジェクトをJersey側でXML文字列に変換し、クライアント側にはXML文字列が出力されます。なお、MIMEタイプをapplication/jsonとすることで、クライアント側にJSONオブジェクトを返すことも可能で、その際、メソッド本体には何の違いもありません。
DBTackleのdbtackle.Databaseクラスの使い方を見ておきましょう。14行目でdbtackle.Databaseクラスのインスタンスを生成しています。17?25行目では、クエリと、クエリの結果を1件ずつマップするクラスを指定しています。このselect()メソッドは、マップするクラスとして指定されたクラスのリストを返します。上記例では、select(Customer.class, …) となっていることから、戻り値は、List<Customer>となります。32行目でデータベースコネクションリソースを開放します。このように、DBTackleによって、従来のデータベース処理が大幅に簡略化されていることを実感していただけると思います。
さて、これで全ての開発は完了です。NetBeans IDEより、プロジェクトを右クリックして「実行」としましょう。
6. 実行
今回、クライアントはブラウザ「Internet Explorer 8 RC1」を使うことにします。XML文字列を確認するだけでしたら、手軽ですからね。私がこの記事を書く上で作成した環境ではURL入力欄に「http://localhost:8080/Yumewaza1/resource/customer」と入力しました。
7. 最後に
いかがでしょうか。XML生成もJDBCの操作も必要とせず、URIとメソッドのマッピング、データベースとクラスのマッピング、クラスとXMLのマッピングをアノテーションで行うだけでRESTful Webサービスが作れてしまいます。これならば、機能開発の横展開も容易ですね。
本記事で作成したソースコード等は、以下のリンクからダウンロードしていただけます。なお、この記事で作成したソースコード、ライブラリ等の使用により起こった事に、筆者はなんら責任を負わないものとします。自己責任でお使いください。
test.Customer
test.Sex
test.DatabaseImpl
test.CustomerResource
データベーステーブル作成クエリ
DBTackle.jar
第2回は、参照系サービスを追加し、Silverlightでクライアントを作ってみようと思います。