2015年5月28日木曜日

【GAE】初級実装編4 一意制約(更新パターン)

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

現在はクラウド基盤「Google App Engine(以下、GAE)」の連載中です。

今回も前回に引き続き一意制約についてです。

やっぱりGAEに一意制約は無い


前回にもテーマにしましたが、GAEに一意制約はありません。
しかし主キー制約はあります。
よって、主キーのみのテーブルを作って主キー制約を実現するしか無いです。

まずは前回にも記載した新規登録の一意制約ソースを以下に張ります。

public Key insert(Shohin model) throws Exception {

  /*
   * トランザクション開始
   */
  Transaction tx = Datastore.beginTransaction();

  try {

   Key key = null;

   /*
    * 一意制約専用テーブルに登録する。
    */
   if (Datastore.putUniqueValue(Shohin.UNIQUE_SHOHIN_NAME, model.getShohinName())) {

    /*
     * 一意制約専用テーブルに登録が成功した場合
     */
    key = super.put(model);

   } else {

    /*
     * 一意制約エラー
     */
    throw new BusinessCheckException("対象の商品名は既に登録されています。");

   }

   /*
    * 正常な場合はコミットする。
    */
   tx.commit();

   return key;

  } catch (Exception e) {

   /*
    * エラーになったらロールバックする。
    */
   tx.rollback();
   throw e;

  }

 }


フローを手短に表現すれば以下になります。

①一意制約テーブルに新規登録する。
⇒失敗したら処理終了。
⇒成功したら次に進む。
②新規登録する。

でも、これって新規登録ですよね。

更新の時はどうやるの?というのが今回のテーマです。

ガチンコ勝負

実はこのブログは、執筆前にネットサーフィンしてネタをかき集めてきておりまして、
上記のソースも、実は本質的には他サイトのパクリのようなもの。(自分で書き直していますけどね)

上のソースの元となった、新規登録系のサンプルソースはネット上に沢山転がっていました。

しかし、どれだけ探しても「更新系」の話題がサッパリ見つからないのです。

恐らく、「更新系」については、特に便利な機能なんか存在しないのでしょう。
ガチンコでロジックを組み上げる以外に手は無いというのが私の結論です。

気合い入れて行きましょう。

ロジック


正確に言いますと、GAEにあるのは「put」と「delete」ですから、「新規登録/更新」などという区分けは技術的にはありません。
しかし人間の操作感では「新規登録/更新」では別物でしょう。
「put」を「新規登録/更新」で使い分けるには、以下ロジックが必要になります。


・対象データを検索する。
⇒データが存在していなければ新規登録。
⇒データが存在していれば更新。


一回find処理が必要になります。普通のSQLみたいにupdateは存在しません。

その上で、delete&insertで一意制約ロジックを実現することになります。

そのフローはこちら。

①一意制約対象のカラムが更新前と更新後で違うかどうかを判定する。
⇒同じである場合は普通にputして終わり。
⇒違う場合胃は次に進む。
②一意制約テーブルに更新後のカラムを新規登録する。
⇒失敗したら処理終了。
⇒成功したら次に進む。
③putする。
④一意制約テーブルに更新前のカラムを削除する。

一意制約カラム削除のメソッドは「Datastore.deleteUniqueValue」です。

これを使用したソースはこちら。

public Shohin update(Shohin updateModel) throws Exception {

   Transaction tx = Datastore.beginTransaction();

   try {

       /*
        * 既存を取得
        */
       Shohin model = super.get(updateModel.getKey());
       model.setEntityUpdate(updateModel);
       super.put(model);

       /*
        * 一意制約項目が既存と同じであれば、そのまま更新する。
        */
       if (StringUtils.equals(model.getShohinName(), updateModel.getShohinName())) {

           model.setEntityUpdate(updateModel);
           super.put(model);

       } else {

           /*
            * 一意制約項目と異なる場合は、更新を行ってから過去の一意制約照合用レコードを消す。
            */
           if (Datastore.putUniqueValue(Shohin.UNIQUE_SHOHIN_NAME, model.getShohinName())) {

               model.setEntityUpdate(updateModel);
               super.put(model);
               Datastore.deleteUniqueValue(Shohin.UNIQUE_SHOHIN_NAME, updateModel.getShohinName());

           }

       }

       tx.commit();

       return model;

   } catch (Exception e) {

       tx.rollback();
       throw e;

   }

}

普通のSQLであればUPDATE一発で済むのに、GAEではこんなにロジックを作らなければなりません。
超面倒ですね!!

GAEの特性を認識せよ


これにより、GAEには個性があるということがお分かり頂けたかと思います。
要点は二点です。

  • GAEで一意制約を実現するのは大変である。
  • GAEはputであって、insert/updateではない。新規登録と更新を分けるのは大変である。

要件定義する時に、エンジニアがこの辺りの事情に配慮して定義しなければいけないのです。

やっぱり、基本的にGAEの一意制約は、本当に本当に必要な場合以外は適応しないというのが大原則と考えて良さそうです。

終わりに

なかなかGAEの個性に悩まされた要件でした。

次はちょっと変わった実装、「メール送信」についてご紹介します。

2015年5月18日月曜日

【GAE】初級実装編3 一意制約(新規登録パターン)

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

現在はクラウド基盤「Google App Engine(以下、GAE)」の連載中です。

今回は一意制約について検証してみます。

GAEに一意制約は無い


「一意制約とは何か」は、割愛させて頂きます。
(読者ならみんな知っているでしょう)

とにかく、結論から言わせて頂ければ、GAEに一意制約はありません。

GAEのBigTableは、要するにJavaの動きで言う所のMapだからなんでしょうね。

Mapは「key」を基準にvalueを登録しますよね?
だから「keyが重複しているか?」は主キー検索一発でチェック出来ますし、重複も物理的にありえません。

しかし、valueの重複が無いかはmap総当たりでチェックするしか無いのです。
GAEも同じような状況にあるのです。

BigTableで一意制約はありません。不可能です。

しかし、BigTableはデータベースですから、本当に厳密な一意性を確保しなければならないシチュエーションも存在します。

そこで活躍するのが、「一意制約専用テーブルを作って主キーを一意制約みたいに使う」というテクニックです。

GAEライブラリであるslim3にはこのテクニックを支援する機能があります。
今回はそれのご紹介です。


Datastore.putUniqueValue


この機能を実現してくれるメソッドは「Datastore.putUniqueValue」、これです。

まずはソースをご覧下さい。

public Key insert(Shohin model) throws Exception {

  /*
   * トランザクション開始
   */
  Transaction tx = Datastore.beginTransaction();

  try {

   Key key = null;

   /*
    * 一意制約専用テーブルに登録する。
    */
   if (Datastore.putUniqueValue(Shohin.UNIQUE_SHOHIN_NAME, model.getShohinName())) {

    /*
     * 一意制約専用テーブルに登録が成功した場合
     */
    key = super.put(model);

   } else {

    /*
     * 一意制約エラー
     */
    throw new BusinessCheckException("対象の商品名は既に登録されています。");

   }

   /*
    * 正常な場合はコミットする。
    */
   tx.commit();

   return key;

  } catch (Exception e) {

   /*
    * エラーになったらロールバックする。
    */
   tx.rollback();
   throw e;

  }

 }

これは「商品名の一意制約チェック」を想定したものです。

いくつかの新登場の情報が出てきていますね。

トランザクション


まず、GAEのトランザクションはコレです。
Transaction tx = Datastore.beginTransaction();

  • Transaction tx = Datastore.beginTransaction();
  • tx.commit();
  • tx.rollback();

読んで字のごとく。説明不要。

疑似一意制約


そして、今回の主題となるのはココです。

if (Datastore.putUniqueValue(Shohin.UNIQUE_SHOHIN_NAME, model.getShohinName())) {

「Datastore.putUniqueValue」

これで「そのキーだけが主キーのテーブル」が作成され、データが投入されます。
ちなみに、Shohin.UNIQUE_SHOHIN_NAMEは単なる固定値。制約のイメージとして一意に定義しています。

/** 商品名一意性約 */
 public static final String UNIQUE_SHOHIN_NAME = "UniqueShohin_ShohinName";


この状況で商品を登録すると、結果はこちら。



「UniqueShohin_ShohinName」というテーブルが作られて、その値は一意制約指定したキー1コだけですね?

こうすることで、主キー制約を一意制約として使い回すことが出来るわけです。


考察

以上のことから分かりますように、「一意制約一個作るのも面倒臭い!!」というのが結論です。
一意制約の数だけテーブル数も増えていってしまいますので、そう気軽に搭載するわけにはいきません。

よって、「何があっても厳密に一意を維持しなければならない!!」という場合を除いて、基本的に一意制約は付与しない方向が良いと思います。

例:メールアドレス

世の中には「メールアドレスをログインIDとして使う」というシステムがあります。
ログインIDが重複しては大変なので、メールアドレスの一意制約は必須。
こういう場合は手間を惜しまず一意制約を搭載しなければなりません。

例:商品名

一方で、この例で出した商品名はどうか?

「同じ商品のレコードが2つ以上あっては業務に混乱を来すから防ぎたい」

という需要があるとしましょう。
しかしですね、商品名なんて重複してたら更新すれば良いのですよ。

この一意制約チェックは「複数ユーザが同時に同じタイミングにレコードを登録した場合」に威力を発揮するもの。

「別々のユーザが同じタイミングで同じ商品名を入れるなんて、まず滅多に考えられない。商品登録前に普通にDB検索チェックするだけで99.999%は防げる。それでも重複した場合は手動修正して貰えばいいや」

という楽観的対処で十分です。

一意制約に厳密性を求めず、ロジックで制御した楽観的一意制約で済ませる。

手を抜ける所は手を抜くのも、GAE開発の一つです。

終わりに

今回は「新規登録」の一意制約をご紹介しました。
しかし、この一意制約はロジックで作り出している擬似的なものですので、「更新」の一意制約はまた違ったロジックを組まなければなりません。

次回は「更新一意制約」についてご紹介します。

2015年4月22日水曜日

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

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

現在はクラウド基盤「Google App Engine(以下、GAE)」の検索機能を検証中です。
今回は主キー検索を行います。

主キー検索


前回の振り返りになりますが、GoogleAppEngineは主キー検索には以下の2パターンがあります。

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

このうち、自動インクリメント風なタイプでの検索を今回は実行します。

検索実行


では、「商品の主キー検索」を行うコントローラ「GetController」を作成します。
そのうち、商品の検索を行っている部分は以下です。

protected ShohinJet doResponse() throws Exception {

 ShohinDao dao = new ShohinDao();

 try {

  Key key = Datastore.createKey(Shohin.class, asInteger("id"));
  Shohin shohin = dao.get(key);

  logger.finer("取得結果:" + shohin.toString());

  ShohinJet jet = new ShohinJet(shohin);
  jet.setResponseCode(ResponseCode.SUCCESS);

  return jet;

 } catch (EntityNotFoundRuntimeException e) {

  throw new ErrorMessageException(ResponseCode.RECORD_NOT_FOUND, "商品");

 }
 
}

実は既に機能の独自フレームワーク化を行ってしまっておりまして、
私以外の人間には意味が分からないソースが含まれている件につきましては、申し訳ありませんがご了承下さい。

さて、上記ソースのうち、検索を行っている部分を抜き出しますと、以下になります。

ShohinDao dao = new ShohinDao();

Key key = Datastore.createKey(Shohin.class, asInteger("id"));
Shohin shohin = dao.get(key);

こうして見ると、何となくやり方がお分かりになるかと思います。

「キークラス」を作って検索するのですね。

キークラス検索

例えば、普通のデータベースクラスの場合、「id=4」であれば、以下のようなSQLを発行します。

SELECT *
FROM SHOHIN
WHERE ID = 4

GoogleAppEngineの場合はキークラスで検索するのです。
キークラスは以下のように「Datastore.createKey」を使って生成出来ます。

Key key = Datastore.createKey(Shohin.class, asInteger("id"));

これで「商品テーブルの主キー」というクラスが生成出来たわけですね。
簡単です。

様々な主キー検索


基本的には上記の検索で要件は足りますが、どうやらSlim3&GAEには色々と便利機能も備わっているようです。
Eclipeでメソッドを調べてみると以下が出てきました。


これらの機能も見ていこうかと思います。

get(List<key> keys)


キーをListで複数渡せるようです。 要するに内部でgetを連発しているのでしょうね。
さて、普通のシステム開発の場合、こういう風にSQLを何度も発行すると負荷が高くなってしまってお薦め出来ません。 潔く範囲検索SQLを発行するのが正解でしょう。
しかし、GAEは大量不可に強いシステムですので、多少こんな風に連発処理してしまっても問題にはなりません。 余程件数が多いでもない限り、ありがたく便利機能を使わせて頂きましょう。

getOrNull(Key key)

エンティティを取得するか、nullを返すという意味です。
最初にご紹介した普通の「get」で検索結果が取得出来ない場合、そのままだとnullが返ってくるのではなくて「EntityNotFoundRuntimeException」が発生します。
それを回避してくれるのがgetOrNullなわけです。

  • get:基本的に取得出来ると想定する場合の主キー検索
  • getOrNull:検索出来ない場合を許容する場合の主キー検索

こういう使い分けになるのです。

getAsync(Key key)


Async。非同期検索機能も備わっているようです。
これはJava本体のマルチスレッドの機能ですね。

処理速度のチューニングを突き詰める為の機能です。

例えば、以下みたいな感じに使います。

/*
* DBの検索開始
* バックエンドで検索するが、この時点で取得が完了するわけではない。
*/
Future<shohin> shohinFuture = dao.getAsync(key);

logger.finer("先にエンティティをしない処理を進めておく。");
logger.finer("先にエンティティをしない処理を進めておく。");
logger.finer("先にエンティティをしない処理を進めておく。");

/*
* エンティティの取得。
* この時点でエンティティを取得出来る。
*/
Shohin shohin = shohinFuture.get();

/*
* エンティティを使った処理を行う
*/
ShohinJet jet = new ShohinJet(shohin);

普通の「get」の場合、取得に処理時間が必要になります。(まあ、一瞬ではありますが)
普通のgetでは取得完了までその場で待機しなければなりませんが、「getAsync」であれば待機せずに先に進みます。

そしてエンティティを使用しなくても良い部分の処理を先に実行しておいて、
後から「get();」により取得する。

これによりDB検索の待ち時間を短縮出来るわけです。

まあ、GAEは元々が高速なので1件だけのgetだとやる価値が無いかもしれませんが、大量処理を行う場合は、こういう機能を使ってチューニングしていきたいものです。

終わりに


引き続きGAEについてご紹介します。

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の『検索』についてご紹介して行きたいと思います。

2014年12月23日火曜日

【JsRender】#parent

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

現在はJavaScriptによる画面レンダリングツール「JsRender」について連載しています。

今回も公式サイトに乗っていない秘密機能「parent」についてのご紹介です。

必要になるシチュエーション

#parentはfor文の真っ最中などで、親クラスを遡ってパラメータを取得する機能です。

例えば、私は「技術開発事業部」の所属ですが、同じ部にはメンバーが何人もいます。

  • 技術開発事業部:Aさん
  • 技術開発事業部:Bさん
  • 技術開発事業部:Cさん

こういう場合、データの持ち方によっては

  • 親クラス:部署
  • 子クラス:メンバー


というように、親子関係が生ずることになります。

このデータの持ち方で所属とメンバーを表現したい場合は、以下のような形式にするのが合理的です。


<div id="result"></div>

<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:department}}
{{for members}}
<div>
・{{:name}}
</div>
{{/for}}
</script>

<script>
var data = {
  "department": "技術開発事業部",
  "members": [
      {"name": "Aさん"},
      {"name": "Bさん"},
      {"name": "Cさん"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>

しかしですね、場合によっては以下みたいな表示にしたいケースもあるわけですよ。


これは痛いんですよね。
何故なら、階層が違うから。

JSONの定義では「members」をfor文のスコープとして回していますので、membersより配下のデータを表示する場合は何も難しくありません。
普通に順を追って定義していけば表示出来ます。

しかし、membersより上のデータはスコープの中に入っていませんので、単純に取得することが出来ません。
今回のテーマである#parentはこういう場合に使用します。

#parentを活用

#parentは、まあ、タグ名から分かりますようにfor文のスコープの外にある親データを取ってくる機能です。
その実装方法はこちら。

<script id="jsRenderTmpl" type="text/x-jsrender">
{{for members}}
<div>
・{{:#parent.parent.data.department}}:{{:name}}
</div>
{{/for}}
</script>

<script>
var data = {
  "department": "技術開発事業部",
  "members": [
      {"name": "Aさん"},
      {"name": "Bさん"},
      {"name": "Cさん"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>

「#parent.parent.data.department」、これで「技術開発事業部」という値が取れています。

詳細解説

「あれ? parent2連打になってるのは何?」と思われたかと思います。

jsrenderでは「parent」を一個各毎に階層を一個遡るという意味になります。

今回の場合、スタート地点となるのはfor文の中のメンバーですから、


  • 単にそのレコードを出したい場合⇒「#data.name」(dataは通常省略)
  • その一個上のmembersを取りたい場合⇒「#parent.data」
  • その一個上のdataを取りたい場合⇒「#parent.parent.data」
  • 目的のdepartmentはdataの子供なので⇒「#parent.parent.data.department」

こういうわけです。
parentを連結することで一回一番上まで遡って、それからdepartmentを狙い撃ちするという段取りになるわけです。

parentを連発する辺り最初は戸惑いますが、分かってしまえば簡単なことですね。

終わりに

今回ご紹介した#parentは結構便利な機能です。
これを知らないと画面を表示する為にクラスを非正規化しなければならなくなり、手間も転送データ量も増えて大変ですからね。
上手く活用すれば開発効率もUPするでしょう。

ぜひマスターすることをオススメします。