2015年1月26日月曜日

【GAE】初級実装編2 主キー検索 その1

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

今週からはクラウド基盤「Google App Engine(以下、GAE)」の検索機能についてご紹介したいと思います。
今回は基本中の基本、主キー検索です。

主キーなんてあったっけ?


さて、いきなりですが、ここでGAEの個性が出て来ます。


「いつ主キーなんて定義したんだ?」



これですね。
普通のDBでの開発の場合、先にDB設計で主キーを定義して、Javaではそれを主キーであると認識の元で使うというやり方です。
つまり、「Javaから見たら主キーかどうかなんて分かりゃしないが、プログラマーが主キーとして使うように頑張る」、これが基本です。

(まあ、HibernateとかiBatisとかDBマッピングライブラリを使いますと、Javaの実装上で主キーであることが明示的になるよう制約を入れてくれたりするわけで、普通はそういうライブラリを使う物ですけどね。)

ともかく、私が言いたいのは、「DB設計なんか無いGAEで主キーの定義はいつやるんだ?」ということです。

それは、これ。
Javaのモデル中にベタ書きするんです。

/** キー */
 @Attribute(primaryKey = true)
 private Key key;

「@Attribute(primaryKey = true)」というアノテーションがありますね。
これが主キーであることを指し示します。

そしてその下、「Key」クラス。これが主キー型です。


StringやIntegerを主キーとして使うのでは無く、Keyという主キー型が存在する。


これがGAEの特徴です。


どうやってパラメータをセットするの?


次の疑問です。

「主キーには商品IDをセットしたいんだけど、Key型って何よ?」

そうです。
主キーが「商品ID」なら、普通なら主キーは「String shohinId」でしょう?
でも、GAEの場合は「Key key」ですので、そう単純ではありません。

GAEの場合、主キーは2種類のやり方があるようです。


  • 自動インクリメント
  • 明示的にセット

この2つです。
そして「自動インクリメント」の方がGAEの標準です。
今回は初級編ですし、後者「明示的にセット」の方は後回しとし、自動インクリメントの方から調査していきます。

自動インクリメント

システム開発をやっている過程で、「主キーは数字。1から順にカウントアップする」という設計を見たことはありませんか?
そう、自動インクリメント主キーですね。

その場合、主キーとなるその数字は「ID」というカラム名が振られていることが多いです。


  • ID:自動シーケンス番号。1から順にカウントアップする。
  • shohinId:主キーではなく一意キー。


こういう作りになっているケースです。

はい?

「shohinIdで一意なんだから主キーとして使えよ」って?

ごもっともです。
DB設計としてはそれが一番合理的。
何でわざわざ商品ID以外に別の主キーを用意せねばならんのやら……。

これには実装側の都合があるのですよ。

DB専門のDBスペシャリストがDB設計すると、必ず「主キーはshohinId」になりますよ、絶対。
でも、実装まで考慮するJavaプログラマーがDB設計をやった場合、必ずしもそうはなるとは限りません。


何故なら、主キーってのは「複数個のカラムで一個の主キー」というケースもありますから。

その場合、Javaのソースはこうなります。

Entity entity = dao.get(主キー1,主キー2,主キー3,主キー4,主キー5);

主キー多過ぎでソースがグダグダ。

でも、自動インクリメントのIDを主キーにしたら?

Entity entity = dao.get(id);

スッキリしましたね!!
そう、IDで一発なんですよ。
「テーブルの主キーは全部ID」「主キー検索=ID検索」という制約の元でDB設計することにより、実装効率が高まるのです。

なので、私はこのID使用のDB設計を好みます。
今回の連載とは関係ありませんが、あの有名な「Ruby On Rails」もこのID使用のDB設計を前提とするものです。
プログラマーとしては一考の価値のある思想です。

ともかく、GAEにもID思想は存在しまして、それがGAEの基本です。

自動インクリメントですかあ、特に明示的にセットする必要はありません。
過去にご紹介した「DB登録編」でも、特に主キーについてはアピールが無かったのもその為。

DBに保存した時に、勝手に主キーは入っている

登録時は主キーを気にする必要は無いのです。

ログを見てみましょう。



toStringすることにより「key=Shohin(57)」と、バッチリ主キーのシーケンス番号が入っていることが分かりますね。

この数字のパラメータ名はID、型はlong型です。
以下のやり方で取得出来ます。

long id = shohin.getKey().getId();

検索の時は、このIDで検索してあげれば良いんです。
分かれば簡単ですね。


終わりに

今回で「主キーとは何ぞや」ということが分かりました。

次回は実際にこれを使って検索します

2015年1月15日木曜日

【GAE】初級実装編2 第二ラウンド開始

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

さて、先週まで画面レンダリングツール「JsRender」について連載していましたが、
今週からは再びクラウド基盤「Google App Engine(以下、GAE)」の連載に復活しようと思います。

今までのおさらい

GAE連載の最後は以下でした。


GAEのデータベースである「BigTable」にデータを叩き混む所までは出来ているんですね。
しかし、データは入れただけでは何の意味も無く、出力出来なければなりません。

出力は以下の二段構えになります。

  • 検索する。
  • 画面に出す。

このうち、「画面に出す」はGAEの機能ではなく、上述の「JsRender」によりフルAjaxにより実現します。
「JsRender」の連載は「検索→表示」の記事を書く為の前フリだったわけです。

JsRendeの記事は以下にまとめていますので、未読の方はご一読下さい。


第二ラウンド開始

そんなわけでいよいよ本命のGAEの記事に戻るわけですが、かなり期間が空いてしまったので一旦仕切り直し。
ここからは第二ラウンド「初級実装編2」として連載を進めて行こうと思います。

この第二ラウンドで、無事にレコードの「登録⇒検索⇒表示」の一連の流れは完成して、初級実装編は卒業ということになります。

しかしながら、私が思うに、検索の方は登録よりも難しいです……。
以下にサラッと触りを記載します。

登録は簡単

以前の記事の転載になりますが、GAEというのは、「登録・更新」は簡単だと思いますよ。

ShohinDao dao = new ShohinDao();
dao.put(shohin);

これだけですから。

Shohin shohin = new Shohin();
Map map = new HashMap();
map.put("ID_0001",shohin);

これとソックリ。
Mapにデータを叩き込むだけ。

まあ、一意制約チェックとかを実現するには少々ステップを伴うこともありますが、
基本的にはこれだけです。

普通のDBであれば、「大量データを一括登録する為にバッチインサート文を作って……」とか「SELECT INSERTで一括して登録を……」みたいな感じに、
大量データ登録を高速化する為に色々と頭を捻ったりすることも無く、
ただボコボコとJavaのクラスそのまま叩き込んでいくだけのパワー勝負です。

しかし、検索は違います。
検索は非常にテクニカルでエンジニアの力量が試されます。

検索の制約

GAEの検索は制約が非常に多いです。

リレーショナルデータベースではない。

「データベースとはリレーショナルデータベースのことだ!!」くらいの勢いで考えているエンジニアは非常に多いと思いますが、
OracleやPostgreSqlみたいなリレーショナルデータベースではありませんので、その常識の根底が根こそぎ崩れます。

結合出来ない。

「SELECT * FROM table1,table2 WHERE TABLE1.id = TABLE2.id」みたいなselect文での結合はSQLの常識ですが、
リレーショナルデータベースではないGAEはsqlもありませんので、こういう結合は出来ません。

常に「table1ならtabe1だけ」という単表検索です。

早い話がJavaのMapみたいなものなんです。
Mapにmap同士を結合する機能なんかありませんよね?

結合させたい場合は、キーによる一意検索を乱射してロジックで結合させるのです。

インデックス検索

ここが重要。

普通のリレーショナルDBでは「インデックスを利用した検索」というテクニックがあります。
これを理解しているエンジニアとしていないエンジニアでは、SQLの検索速度が段違い!!
軽く数千倍の差が出ることも珍しくありません。

GAEの場合はこれに特化していまして、インデックス検索しか無いのです。

前方一致検索は出来ても後方一致検索は出来ない

「where table1.name lile '%tacy%'」みたいに前後方一致検索は普通のSQLでもありますが、
この場合、インデックスが効いていません。
よって検索は遅いです。

「where table1.name lile 'tacy%'」と、前方一致検索ならばインデックスが効きますので高速です。

これと同じことがGAEでも発生しまして、後者の前方一致検索であればGAEならではの高速検索が作動します。
前者の前後方一致検索は実行不能ですので、全件取得してJavaでフィルタリングする、という荒業を行うハメになります。

大量件数では実現出来ませんね。
このように、インデックスで実現出来ない検索は出来ないというのがGAEの基本です。

count(*)が無い

これが驚きなのですが、count(*)は無いのです!!
単純に検索条件で全部取得して、Javaで「++count」みたいに一件一件数えるしか無いです。

GAEの制約の中でもこれは結構ダメージが大きいかと。
「画面上に総件数を出す」みたいな要件への対応は難しいかと。

ORも無い

OR条件が出来ません。2回検索してJavaでマージする必要があります。




とまあ、ザッと目ぼしい所を挙げるだけでもGAEの検索にはこんなに沢山の制約があります。

これを乗り越える為には、

  • そもそも出来ないことを要件に入れないようにする。
  • テーブル設計を冗長化することにより結合を発生させない。
  • 検索キー専用のカラムを容易する。例えば後方一致検索を行う為に文字列を前後反転させて保存しておくなど

などなど、技術と言うよりサーカスと言った方が良いんじゃないかというような技を駆使することになります。

これを聞いただけでゲンナリする人もいらっしゃるかもしれませんが、いえいえ、諦めてはいけません。
Googleの誇るGoogle検索や、Google+や、このブログ、GMailなど、これらは全部そういう課題を乗り越えて作っているのですから。
頑張ればちゃんと出来るはずなのです。

気合入れて行きましょう!!

終わりに

というわけで、初級実装編2は、実質的には検索編となるでしょう。

来週から実際に色々と要件を決めてサンプルを実装していこうと思います。

2015年1月8日木曜日

【JsRender】GAEとの連携

あけましておめでとう。
株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

本年もよろしくお願いします。

さて、ここしばらくはJavaScriptによる画面レンダリングツール「JsRender」について連載していましたが、
それも今回で最終回を迎えます。

最終回はGAEとの連携です。

思い出す

過去の記事を掘り返してみたら、GAEの記事を最後に書いたのは2014年9月でした。
もう4ヶ月も経ってしまったのですね……。

そして、そのGAEでの連載で私が何度も主張していたこと。それは。

GAEはjspを使わず、フルAjaxで書いてこそ真価を発揮する。

これでした。
そして、そのGAEの連載を中断して始めたのが、本連載である画面レンダリングツール「JsRender」のご紹介……。

もうお分かりですね。

この一連の連載は、JsRenderをGAEで使用する為の前振りだったのです。

ようやく本懐を遂げること出来ました。
では、さっそくGAEに適用してみましょう。

JSON発行

GAEからJSONを発行する方法は過去の記事で紹介しておりましたね。



昔の記事なので改めて書きますが、
GAEでは「GSON」というライブラリを使ってJavaのオブジェクトをJSON文字列化するやり方をオススメします。

Gson gson = new Gson();
String json = gson.toJson(jet);

その結果、以下のようなJSON文字列が出力されます。

{"shohinList":[{"key":{"kind":"Shohin","id":57},"seqNo":1,"shohinId":"57","shohinName":"%E5%95%86%E5%93%813","stock":345,"deliveryDate":"2015/01/10","createdAt":"2015/01/08 21:45:28","updatedAt":"2015/01/08 21:45:28"},{"key":{"kind":"Shohin","id":56},"seqNo":2,"shohinId":"56","shohinName":"%E5%95%86%E5%93%81%EF%BC%92","stock":234,"deliveryDate":"2015/01/09","createdAt":"2015/01/08 21:45:15","updatedAt":"2015/01/08 21:45:15"},{"key":{"kind":"Shohin","id":55},"seqNo":3,"shohinId":"55","shohinName":"%E5%95%86%E5%93%81%EF%BC%91","stock":123,"deliveryDate":"2015/01/08","createdAt":"2015/01/08 21:44:48","updatedAt":"2015/01/08 21:44:48"}],"responseCode":0}

ゴジャゴジャしていて分かりませんが……。
実は「日本語はURLエンコードしないと文字化けして送受信出来ない」とか色々問題があるのですが、その辺については後日の連載で。

ともかく、この文字列を以下のやり方でレスポンスに乗せてあげれば、Ajaxでレスポンスを取得することが出来ます。
response.getWriter().write(json);

画面出力

文字列さえ出力出来てしまえばこっちのものです。

標準的なJQueryの非同期通信で文字列を取得し、そのままJsRenderに流し込んでしまいます。

function doSearch() {

 $.ajax({
  url : $("#form").attr('action'),
  type : $("#form").attr('method'),
  data : $("#form").serialize(),
  dataType : 'json',
  success : function(json) {

   var items = json.shohinList;

   var length = items.length;
   for (i = 0; i < length; i++) {
    items[i].shohinName = decodeURI(items[i].shohinName);
   }

   $("#shohinList").html($("#shohinListTemplate").render(json));
  }
 });
}
JsRenderのテンプレートはこちら。
<script id="shohinListTemplate" type="text/x-jsrender">

<table class="shohinList">
 <tr>
  <th class="no">NO</th>
  <th class="shohinId">商品ID</th>
  <th class="shohinName">商品名</th>
  <th class="action"></th>
 </tr>
 {{for shohinList}}
 <tr>
  <td class="no">{{>seqNo}}</td>
  <td class="shohinId">{{>shohinId}}</td>
  <td class="shohinName">{{>shohinName}}</td>
  <td class="action">詳細 編集</td>
 </tr>
 {{/for}}
</table>

</script>

>/script<

結果は、ほら。
普通のjspと比較しても遜色無い機能的な検索結果一覧が出力出来ました。


これでGAEの画面出力も安心ですね。

終わりに

ようやくGAEに戻って参りました。

画面出力機能の骨子が出来上がったことですし、
次回からは再びGAEに復帰。

GAEのDBであるBigTableの『検索』についてご紹介して行きたいと思います。