2012年11月29日木曜日

テンプレート・エンジン {{ mustache }} を使ってみる

{{ mustache }} というテンプレートエンジンを知っていますか?
http://mustache.github.com/

Google Trendで、Javaで有名なテンプレート・エンジンである Apache Velocity と比較したところ、最近では人気度が上回っているようです。



mustache はHTMLやコンフィグファイル・ソースコードを生成するためのシンプルなテンプレートエンジンで、以下のようないろんな言語をサポートしているようです。
Ruby, JavaScript, Python, Erlang, PHP, Perl, Objective-C, Java, .NET, Android, C++, Go, Lua, ooc, ActionScript, ColdFusion, Scala, Clojure, Fantom, CoffeeScript, D, node.js.

ちなみに、「mustache」とは「口ひげ」という意味で、「{」をタグとして利用したテンプレートということから、「{」を横にしたらひげに見えるから「mustache」と名前が付いたようです。



変数定義
早速どういったもの見てみましょう。以下はマニュアルに載っていたサンプルです。

・テンプレート
Hello {{name}}
You have just won ${{value}}!
{{#in_ca}}
Well, ${{taxed_value}}, after taxes.
{{/in_ca}}

・上記のテンプレートに渡すデータ(hash)をJSONもしくはYAMLで定義します。
{
    "name": "Chris",
    "value": 10000,
    "taxed_value": 10000 - (10000 * 0.4),
    "in_ca": true
}

・結果は以下のように表示されます。
Hello Chris
You have just won $10000!
Well, $6000.0, after taxes.

このテンプレートの「{{」と「}}」で囲まれたタグが、渡したデータ(hash)により変更され、結果として出力されます。上記の例では、{{name}}というタグ部分が、データの"name"の値「Chris」に変更されます。同様に{{value}}タグは、"value"の10000になります。


ちなみに、デフォルトで結果はエスケープされます。(例:「<」や「>」は、「&lt;」や「&gt;」に。)これをエスケープしないためには、ひげを3つにするか(例.{{{tag}}})、「{{&」と「}}」で囲みます。

・テンプレート
{{name}}
{{age}}
{{company}}
{{{company}}}

・データ(hash)
{
  "name": "Chris",
  "company": "<b>GitHub</b>"
}

・結果
Chris
&lt;b&gt;GitHub&lt;/b&gt;
<b>GitHub</b><


条件式やループ
mustacheが、「logic-less templates」というように呼ばれている意味は、どうやらif文やfor/while文などのステートメントが含まれないことからロジックレスという名称が付いているようです。ただし、テンプレート・エンジンでステートメント的なものは必要なので、条件文やループなどがどのように表現されているか説明しますと、「{{#」と「}}」で囲むことで実現できるようです。(ちなみに、ブロックの閉じるタグは「{{/」と「}}」で囲む。)

上記の例ですと、{{#in_ca}}と{{/in_ca}}にあたります。この場合、データ"in_ca"はboolean型なので、if文のように動きます。"in_ca"がtrueなら「Well, ....」が表示され、falseならこの部分が表示されません。(もしくは値が無い場合も表示されません。)

では、ループはというと、これも同様に「{{#」と「}}」で囲みます。ただし、この囲まれたタグの値の型がリストの場合に繰り返されます。以下はサンプルですが、"repo"の値がリストですので、以下のようにリスト分、繰り返されます。

・テンプレート
{{#repo}}
  <b>{{name}}</b>
{{/repo}}

・データ(hash)
{
  "repo": [
    { "name": "resque" },
    { "name": "hub" },
    { "name": "rip" },
  ]
}

・結果
<b>resque</b>
<b>hub</b>
<b>rip</b>

さらに、関数を渡すこともできるようです。これは面白いですね。

・テンプレート
{{#wrapped}}
  {{name}} is awesome.
{{/wrapped}}

・データ(hash)
{
  "name": "Willy",
  "wrapped": function() {
    return function(text) {
      return "<b>" + render(text) + "</b>"
    }
  }
}

・結果
<b>Willy is awesome.</b>

このタグがbooleanでもなく、リストでもない場合に、tagに相当するキー値が存在すれば、以下のようにtrueと同様の動きをするみたいです。

・テンプレート
{{#person?}}
  Hi {{name}}!
{{/person?}}

・データ(hash)
{
  "person?": { "name": "Jon" }
}

・結果
Hi Jon!

また、if文の逆(not if)は以下のように「{{^」と「}}」で囲んで書きます。

・テンプレート
{{#repo}}
  <b>{{name}}</b>
{{/repo}}
{{^repo}}
  No repos
{{/repo}}
Hash:

・データ(hash)
{
  "repo": []
}

・結果
No repos

その他のタグ
あと、コメントは「{{!」と「}}」で囲みます。

別のテンプレートファイルとして定義して読み込みたい場合は、例えばother.mustacheファイルを読み込みたいのであれば、{{>other}}のように「{{>」と「}}」で囲みます。

最後に、「{{」と「}}」以外でテンプレートを作りたい場合、以下のようにテンプレート中で記載することで変更ができます。(この例は、「{{」を「<%」に、「}}」を「%>」に変更)
{{=<% %>=}}

元に戻すには、
<%={{ }}=%>

これをmustache.jsで使うには以下のようなコードで動きます。
var template = 'I'm {{name}}.';
var v = Mustache.to_html(
          template,
          {
            name:"hitocie"
          }
);
console(v);

うーん、結構良くできていますし、Apache Velocityなどと比べると非常にシンプルですね。

今回、ほぼマニュアルの翻訳的な感じになってしまいましたが、ある程度の機能は紹介できたかと思います・・・。

ちなみに、mustacheを拡張したもので、Twitterからオープンソースとして提供されているhogan.jsというものがあります。これは、mustacheの記法で書いたテンプレートをAOT(Ahead Of Time)コンパイルして、JavaScriptに変換するというもので、レンダリングのたびにmustache記法のテンプレートを解析する必要がなく高速に動作するため、WebシステムでJSPなどのように利用できます。


2012年11月24日土曜日

Node.jsが面白い件③ MongoDBで遊ぶ 〜基本編〜

今回はMongoDBの使い方についてです。MongoDBは有名なNoSQLの1つで、Redisと同じようにNode.jsでも簡単に使えます。MongoDB Native NodeJS Driverをダウンロードし、こちらのマニュアルに沿って記述すれば基本的な使い方はO.K.です! ・・・で終わると書いている意味もないので、ここでは基本的な使い方を記述して行きます。 まずはいつも通りnpmでインストール!
 > npm install mongodb

続いて、データベースに接続してコレクションを作成した後ドキュメントを挿入し、コレクションを削除するという簡単なサンプルプログラム。
var mongo = require('mongodb');

// ドキュメントサンプル。
var doc1 = {'key1':'val1'};
var doc2 = {'key2-1':'val2-1', 'key2-2':'val2-2'};
var docs = [doc1, doc2];

// serverの設定。
var server = new mongo.Server('localhost', 27017, {auto_reconnect: true});

// dbの設定。
var db = new mongo.Db('exampleDb', server, {safe:true});
//var db = new mongo.Db('exampleDb', server);

var closeDB = function() {
    db.close(true, function(err, result) {
        console.log("close!");
    });
}

// コネクションオープン。
db.open(function(err, db) {
    if (!err) {
        console.log("open!");
        // コレクションの作成。既に存在する場合はエラー。
        db.createCollection('collection1', {strict:true}, function(cerr, collection) {
            if (!cerr) {
                console.log("create!");

                // ドキュメントの格納。
                collection.insert(docs, function(ierr, res) {
                    if (!ierr) {
                        console.log("insert doc1!");

                        // ドキュメントの検索。
                        collection.find().toArray(function(ferr, items) {
                            if (!ferr) {
                                console.log(items);

                                // コレクションの削除。
                                collection.drop();
                                closeDB();
                            } else {
                                console.log(ferr);

                                collection.drop();
                                closeDB();
                            }
                        });
                    } else {
                        console.log(ierr);

                        collection.drop();
                        closeDB();
                    }
                });
            } else {
                console.log(cerr);

                closeDB();
            }
        });
    } else {
        console.log(err);
    }
});

MongoDBでは、今のところデフォルトで書き込みの結果を待たずにリターンを返します(safe=false)。つまり、書き込みが成功したか失敗したかがすぐにわかりません。これは、データベースを作成する際にsafe=trueを設定することで変更できます。また、書き込みの挙動は以下のように設定することができます。
  1. j:ジャーナル(redoログのようなもの)のコミットを待つ。ジャーナルが設定されていなければそのまま返す。
  2. w:レプリカに指定数反映されるのを待つ。タイムアウト指定も可能。
  3. fsync:ジャーナリングされてない場合は全てのファイルをfsyncするのを待つ。ジャーナリングしている場合は次のグループのコミットを待つ。非推奨なのでjを使うべき。

MongoDBでは動的にcollectionを作成するケースも多いですが、既に作成しようとするcollection存在する場合はエラーを受け取りたい、という場合もあります。その場合はcreateCollectionでstrict:trueを指定します。
  1. createCollectionでstrict:false(デフォルト)の場合、既に存在する場合は何もしない(作成しない)
  2. createCollectionでstrict:trueの場合、既に存在する場合はエラーを返す。
  3. collectionでstrict:false(デフォルト)の場合、コレクションが存在しない場合は最初のinsertで作成する。
  4. collectionでstrict:trueの場合、コレクションが存在しない場合はエラーを返す。
サンプルプログラム内ではinsert/findを実施しています。この辺りはupdateやdeleteも含めて多くの処理がありますので、ドキュメントを見ながら実装していけばよいと思います。
Node.jsというよりMongoDBの話になりましたが、他の言語でMongoDBを使っている開発者ならかなり違和感なく使えそうですね。また、MongoDBはWeb上にもドキュメントが揃ってきていますので、NoSQL初心者でも比較的情報は得やすいですね。

2012年11月22日木曜日

BaaS(Backend as a Service)のプッシュ通知サービス

最近、BaaS(Backend as a Service)っていう言葉をよく聞きます。これは、スマートデバイス(モバイル)アプリケーション向けに、サーバサイド実装の手間を省くため、データベースへの保存・参照、SNSとの連携、プッシュ通知、認証、ファイル保存などの機能を簡単に呼び出すことができるサービスです。(実際はモバイルのみが対象ではないものもあり、本当にモバイル特化の場合は、MBaaSという呼び方をする場合もあるそうです。)これらのサービスにはSDK(開発キット)が提供されておりAPIをコールするだけで呼び出すことができたり、RESTでアクセスする機能が提供されています。
今回は、そのBaaSの中の1つのサービス「プッシュ通知」機能について、各ベンダーごとにどのようなサービスを提供しているのかを見ていきたいと思います。

その前に、プッシュ配信の仕組みについては、ほとんどのベンダーがApple、Google、Microsoftが提供するサービス「APNS(Apple Push Notification Service)」、「GCM(Google Cloud Messaging for Android)」、もしくはMicrosoftの「WNS(Windows Push Notification Service)」をベースに作っています。簡単に言えばラップしているだけのサービスなので、以下のようにBaaSベンダーはプッシュされたメッセージを各ベンダーごとに振り分けるというようなサーバを用意しているというイメージになります。


このことから、以下のページを参考にして、自分で実装することもできます。

ただ、簡単になっていますので、BaaSを使った方が便利ではありますね。

では、各BaaSベンダーのサービスです。

Microsoft Mobile Services
上記でも軽く触れました名前の通りのMicrosoftがAzure上で提供する「Windows Push Notification Service (WNS)」というサービスです。そのため以下のようにWindowsがサポート対象ですが、将来的にはAndroidやiOSにも対応するようです。
・プラットフォーム: Windows Store, Windows Phone
・言語: C#など

ACS(Appcelerator Cloud Services)
モバイル開発ツールで有名な「Titanium Mobile」を作っている会社「Appcelerator」がCocoafishという会社を買収して、ACSという名称で提供しています。
・プラットフォーム: Android or iOS
・言語: JavaScript, REST, AS3, Java for Android

Buddy
ここのサービスは、他とは違いAndroidとiOSに加え、Windows系もサポートしています。内部的には上記のWindows Push Notification Service(WNS)を使っているようです。
・プラットフォーム: Android, iOS, Windows Store, Windows Phone 
・言語: Java, Objective-C, C#

appiaries(国産)
国産のBaaSのようです。ただ、プッシュ配信は、まだ未提供のようです。将来的にはAndroid/iOS対応予定とのこと。

Parse
・プラットフォーム: Android, iOS
・言語: REST, JavaScript, Java for Android, Objective-C for iOS

StackMob

・プラットフォーム: Android, iOS
・言語: Java for Android, Objective-C for iOS, REST
   
Kinvey
http://www.kinvey.com/
・プラットフォーム: Android, iOS
・言語: Java for Android, Objective-C for iOS

その他、ApplicasaQuickbloxSencha.ioなどの有名なBaaSがありますが、あまり変わらなそうなので、とりあえずこれらは未調査。


上記サービスは基本的に、APNS、GCM、WNSをベースに作られていますが、プッシュ配信と言えば、HTML5のWebSocketが有名です。そこで、WebSocketをベースにプッシュ配信サービスを提供しているベンダーを少し挙げてみます。

pusher
PHP, Ruby, JavaScriptなどWebSocket ClientのAPIがあればアクセス可能

Kaazing
http://kaazing.com/
プッシュ通知サービスは、Kaazing WebSocket Gateway (HTML5 Edition)と呼ばれるようです。こちらもWebSocket Clientのライブラリからアクセスできますが、あらかじめ、Java, JavaScript, .Net, SilverLight, AIR, Flex, Flashなどのライブラリが用意されているようです。また、WebSocket以外にも、JMS, AMQP, XMPPもサポートしています。(ただし、WebSocketほどメジャーではないですが。)

以上がプッシュ通知のサービスですが、今後はBuddyなどのようにAPNS、GCM、WNSを連携するのみでなく、さらにWebSocketなどもサポートされていくのではないかと思います。

2012年11月20日火曜日

iOS デバイスへのプッシュ通知と node.js (プロバイダ編)

iOS デバイスへのプッシュ通知は、APNs (Apple Push Notification service) を使って実装できます。
加えて、配信するコンテンツを提供するサーバーも必要になります。
今回は、ちょうど使ってみたかった node.js で動かしてみます。

APNs の概要


全容を把握したい場合は、やっぱり公式のガイドがいいと思います。

 Local NotificationおよびPush Notificationプログラミングガイド

現時点では日本語版の方が古いということもなく、英語版の Local and Push Notification Programming Guide と同じ内容みたいですので、安心して日本語版を参照できますね。

APNs を利用したプッシュ通知を単純化すると以下のようになります。
(上記、公式ドキュメントからの引用です)


今回 node.js で作るのは、上図のプロバイダに相当するものです。


まずは iOS Provisioning Portal


APNs は実機でしか使えないので、Provisioning Portal で Provisioning Profile を用意します。手順は基本的に通常の iOS アプリ開発時と同じなので割愛。
ただし、App ID については2点ほど注意が必要です。

1. ワイルドカードを使っていない ID を作る
通常 AppID にはワイルドカードの指定が許されていますが、APNs を利用するアプリではワイルドカードは使用できません。

2. 1 の ID で APNs を有効にする
ID 作成後に、Configure から設定に入り、
Enable Push Notification service にチェックを入れて APNs を有効にした後に
証明書(Push SSL Certificate)を取得する必要があります。
証明書には、Development / Production の2つがありますが、今回は Development にします。


プロバイダを node.js で


簡単に APNs を利用できるモジュールがありますので、それを使います。

node-apn
 https://github.com/argon/node-apn

README がしっかりと用意されているので、そちらを読めば使い方は基本的な使い方は把握出来そうです。
READMEからポイントをいくつか転載。

インストールとコード内での利用
これはいつも通りです。
$ npm install apn
var apns = require('apn');

apns.Connection へ渡すオプションについて
デフォルトは以下のようになっています。
var options = {
    cert: 'cert.pem',                 /* Certificate file path */
    certData: null,                   /* String or Buffer containing certificate data, if supplied uses this instead of cert file path */
    key:  'key.pem',                  /* Key file path */
    keyData: null,                    /* String or Buffer containing key data, as certData */
    passphrase: null,                 /* A passphrase for the Key file */
    ca: null,                         /* String or Buffer of CA data to use for the TLS connection */
    gateway: 'gateway.push.apple.com',/* gateway address */
    port: 2195,                       /* gateway port */
    enhanced: true,                   /* enable enhanced format */
    errorCallback: undefined,         /* Callback when error occurs function(err,notification) */
    cacheLength: 100                  /* Number of notifications to cache for error purposes */
};

var apnsConnection = new apns.Connection(options);

  • cert, key には前節 App ID のところで取得した証明書を使う。
  • Development の Push SSL Certificate を使用する場合は、gateway の値を gateway.sandbox.push.apple.com に!
  • enhanced が true の場合、errorCallback が使用できます。(後述)

APNs の証明書
cert.pem, key.pem は以下のコマンドで作れます。

$ openssl x509 -in cert.cer -inform DER -outform PEM -out cert.pem
$ openssl pkcs12 -in key.p12 -out key.pem -nodes
コマンド中の2つのインプットは次のようにして用意します。
cert.cer ... Provisioning Portal の App ID のところで取得したもの(aps_development.cerとか)
key.p12 ... aps_development.cer をダブルクリック等でインストール後、
  Keychain Access.app を起動 -> My Certificates -> Apple Development iOS Push Services を選択 -> File メニューから Export Items を選択
 で取得できます。



errorCallback について
APNs のエラー応答については、前述の公式ドキュメントの「バイナリインターフェイスの形式と通知形式(p.50)」あたりに詳しいです。
node-apn では、オプションの enhanced が true の状態で
errorCallback に function(err, notification) {} を指定することで APNs からのエラーメッセージが拾えます。




feedback について
フィードバックの概要は、公式ドキュメントの「フィードバックサービス(p.55)」で。
var feedback = new apns.Feedback(options);
で簡単に使用できます。

var options = {
    cert: 'cert.pem',                   /* Certificate file */
    certData: null,                     /* Certificate file contents (String|Buffer) */
    key:  'key.pem',                    /* Key file */
    keyData: null,                      /* Key file contents (String|Buffer) */
    passphrase: null,                   /* A passphrase for the Key file */
    ca: null,                           /* Certificate authority data to pass to the TLS connection */
    address: 'feedback.push.apple.com', /* feedback address */
    port: 2196,                         /* feedback port */
    feedback: false,                    /* enable feedback service, set to callback */
    interval: 3600                      /* interval in seconds to connect to feedback service */
};

options も Connection と同じような感じですね。
address は、開発用を使っている場合は Connection と同じように feedback.sandbox.push.apple.com を使用します。
feedback にコールバックをセットするとインターバルの間隔で呼び出されます。この際、コールバックがとる引数は2つ、サーバーが返す time と デバイストークンを含む Buffer です。

debug モジュール
debug モジュール ( https://github.com/visionmedia/debug ) を導入するとデバッグメッセージを有効にできます。
npm install debug.

$ DEBUG=apn node apns.js
のようにすると APNs 接続に関するデバッグメッセージがコンソールに表示されます。
DEBUG=apnfb では、フィードバックに関するメッセージが出力されます。

token
デバイストークン。
長くなってきたので、これについては iOS 側の実装とともにまた次回の記事で。


2012年11月19日月曜日

Node.jsが面白い件② redisを使う

Node.jsが面白い件①の続きです。今回はredisの使い方について書いていきます。redisはオープンソースのインメモリKVSとして有名で、ディスクへの書き込みやトランザクション機能も提供する優れものです。クラスタに関してはまだ安定版が出ていませんが、単体ではとても簡単に利用できます。以下、Macでのインストール手順です。詳しくはredisのページを参照してください。
  1. redisのダウンロードページからダウンロードする。
  2. 展開したディレクトリに移動しmakeを実行する。
  3. 同ディレクトリでmake testを実行する。
node.js側では以下のようにライブラリredis、hiredisをインストールします。例えば作業ディレクトリをRedisSampleとすると、以下のようにします。
 > mkdir RedisSample
 > cd RedisSample
 > npm install hiredis redis

RedisSampleディレクトリにサンプルプログラムapp.jsを作成し、以下の内容を記述します。

//redisの読み込み。
var redis = require('redis')

try {
    // コネクションの作成。defaultで127.0.0.1:6379を使う。
    console.log("-- connection open.");
    var client = redis.createClient();

    // onメソッドによるerrorイベント時の処理。
    client.on("error", function(err) {
        console.log("Error:" + err);
    });

    // password認証。redis.confでrequirepassにredispassを設定。
    console.log("-- auth");
    client.auth("redispass");

    // 全てのkeyを削除。
    console.log("-- flushdb");
    client.flushdb();

    // 文字列のkey-valueとして値を格納。key:key1、value:val1
    client.set("key1", "val1");

    // 格納した値を取得。 key: key1
    client.get("key1", function(err, obj) {
        console.log("get key1: " + obj);
    });

    // リストの保持。key: lkey、value: lval1, lval2
    client.lpush("lkey1", "lval1");
    client.lpush("lkey1", "lval2");

    // 格納したリスト値を取得。key:lkey1
    client.lrange("lkey1", 0, -1, function(err, obj) {
        console.log("get list lkey1: " + obj);
    });

    // ハッシュの保持。key: hkey1、value: hfld1:hval1, hfld2:hval2
    client.hset("hkey1", "hfld1", "hval1");
    client.hset("hkey1", "hfld2", "hval2");

    // 格納したハッシュ値を取得。 key:hkey1
    client.hgetall("hkey1", function(err, obj) {
        console.log("get hash hkey1");
        console.log(obj);
    });


    // トランザクションその1。成功。
    multi = client.multi();
    multi.set("mkey1", "mval1");
    multi.set("mkey2", "mval2");
    multi.exec(function(err, obj) {
        client.get("mkey1", function(err, rep1){
            console.log("get mkey1: "+rep1);
        });
        client.get("mkey2", function(err, rep2) {
            console.log("get mkey2: "+rep2);
        });

        // トランザクションその2。watchの値が更新されて失敗。
        client.set("wkey1", "wval1");
        client.watch("wkey1");
        client.set("wkey1", "wval-upd");
        multi = client.multi();
        multi.set("mkey1", "mval3");
        multi.set("mkey2", "mval4");
        multi.exec(function(err, obj) {
            client.get("mkey1", function(err, rep1){
                console.log("get mkey1: "+rep1);
            });
            client.get("mkey2", function(err, rep2) {
                console.log("get mkey2: "+rep2);
                console.log("-- connection close.");
                client.quit();
            });
        });
    });
} catch(err) {
    console.err(util.inspect(err));
}

結果は以下のようになります。
 > -- connection open.
 > -- auth
 > -- flushdb
 > get key1: val1
 > get list lkey1: lval2,lval1
 > get hash hkey1
 > { hfld1: 'hval1', hfld2: 'hval2' }
 > get mkey1: mval1
 > get mkey2: mval2
 > get mkey1: mval1
 > get mkey2: mval2
 > -- connection close.

2行目で最初にインストールしたredisを読み込みます。6行目でコネクションを作成して、あとはredisのコマンドにならって処理を実行しています。redisはトランザクションをサポートしていますが、51行目のトランザクション①は成功し、63行目のトランザクション②は失敗します。これはwatchを使って楽観的ロックを実現しており、この例の場合はwkey1をwatchしており、watch実行後からexec実行前までにwkey1が変更されているとトランザクションが失敗するため、結果としてはmkey1、mkey2はmval1、mval2のままとなります。
この例の場合、トランザクション①の結果表示でコールバックを使っているので、コールバック内にトランザクション②の処理を記述しています。Node.jsではコールバック処理をよく使うので処理結果の順序を保持したい場合はこのように意識して記述するか、簡素化のためにstepなどのモジュールを使って処理を記述することができます。
redisはNode.jsでは今のところよく使われる組み合わせなのですが、ここで書いたように基本的な使い方はとても簡単です。軽いプロトタイプなら、Node.jsとredisだけですぐに作れてしまいそうですね。

2012年11月13日火曜日

MyBatis/iBATIS用のコードを自動生成する「MyBatis Generator」を使う

前回は、MyBatisの基本的な使い方を見てきました。今回はそこで手作業にて行ってきた各ファイルの自動生成ツール「MyBatis Generator」の使い方を説明していきたいと思っています。

MyBatis Generator Eclipse pluginのセットアップ
EclipseのメニューからHelp -> New Install Softwareを選択し、以下のURLを入力することでEclipse pluginがセットアップされます。
http://mybatis.googlecode.com/svn/sub-projects/generator/trunk/eclipse/UpdateSite/

インストールできましたら、メニューからFile -> New -> Other...を選択し、MyBatis Generator Configuration Fileを指定して、設定ファイル(デフォルトではgeneratorConfig.xml)を生成し、環境に合わせて編集します。このファイルは、Generateするための設定情報です。


  
    
    
    
    
    

各タグの説明ですが、javaModelGeneratorタグはエンティティクラスファイル、sqlMapGeneratorタグはXMLのMapperファイル、javaClientGeneratorタグはMapperクラスファイルの出力先を指定するためのタグです。tableタグで対象とするテーブル名を記述します。このtableNameにはワイルドカードも指定できるようです。ファイル編集後は、このgeneratorConfig.xmlを選択して、右クリックで「Generate MyBatis/iBATIS Artifacts」を選択しますと、エンティティクラスファイルとMapperファイル、Mapperクラス、Exampleクラスの4つのファイルが生成されます。(ちなみにMyBatis用だけでなく、2.2以上のiBATIS以上のファイルを生成できるようです。)

例えば、blogテーブルの場合は以下の4つのファイルが生成。
  • Blog.java :エンティティクラス。
  • BlogExample.java :Generator独自のクラスっぽい。SQL を呼び出す際、条件を指定するために利用するクラス。
  • BlogMapper.java :クエリを実行するための Java インターフェイス。
  • BlogMapper.xml :Mapper XML ファイルで、ほとんどの SQL はこのファイルに記述。
ちなみに、自動生成された JavaとXML ファイルのコメントには@mbggeneratedというアノテーションがあります。MyBatis Generatorが再度コードを生成するとき、このアノテーションが付加された要素だけを削除してから再度新しい要素を生成されます。そのため、このアノテーションがある場合は、手動で修正すると消えてしまいますのでご注意を。 話はそれましたが、続けますとBlogMapper.xmlを前回のように、mybatis-config.xmlのmapperタグに記述されていればデータベースアクセスできるようになります。例えば以下のようなコードでレコードを登録して、件数をカウントするということが簡単にできます。

BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(0);
blog.setContent("Test-Test");
mapper.insert(blog);
int count = mapper.countByExample(new BlogExample());
System.out.println(count);
session.close();

myBatis Generatorをカスタマイズ
このMyBatis GeneratorをPluginを作ることで、カスタマイズできます。具体的にはファイル名やクラス名を変えたり、インターフェースやスーパークラスを追加できたり、メソッド、コンストラクタなどを追加できたりと、結構いろいろ生成するファイルを変更することができます。
まずはここから"Mapping Generator"をダウンロードして、解凍します。そのmybatis-generator-core-x.x.x.jarファイル(私は1.3.2を利用)をEclipseにコピーして、ビルドパスを設定します。
次にorg.mybatis.generator.api.PluginAdapterを継承したクラスを作成します。このクラス内で生成するタイミングに合わせてオーバーライドするメソッドを選択肢、中身を実装します。(例えば、初期化時ならinitializedメソッドをオーバーライド。)以下の例では、エンティティクラスを生成する際に、エンティティクラスにコンストラクタを追加するというサンプルです。

package com.test.generator.plugins;

import java.util.List;

import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.JavaVisibility;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.TopLevelClass;

public class GenPlugin extends PluginAdapter {

 @Override
 public boolean validate(List arg) {
  return true;
 }

 @Override
 public void initialized(IntrospectedTable introspectedTable) {
  super.initialized(introspectedTable);
 }

 @Override
 public boolean modelBaseRecordClassGenerated(
   TopLevelClass topLevelClass,
   IntrospectedTable introspectedTable) {
  Method method = new Method();
  method.setVisibility(JavaVisibility.PUBLIC);
  method.setConstructor(true);
  method.setName(topLevelClass.getType().getShortName());
  method.addBodyLine("System.out.println(\"Called constructor\");");
  topLevelClass.addMethod(method);
  return super.modelBaseRecordClassGenerated(topLevelClass, introspectedTable);
 }
}

この作成したクラスをgeneratorConfig.xmlにcontextタグの中にpluginタグとして、追加します。

  
    
       .....
  

そうすると以下のコンストラクタがBlogクラスに追加されます。

public Blog() {
 System.out.println("Called constructor");
}

このプラグイン機能でいろいろと生成もいじれそうなので、開発の生産性向上に役立ちそうですね。

外部キーなどの対応
ここまで来たら、以下のように参照関係のあるテーブルの場合に自動生成してくれるかを試してみようと思います。

CREATE TABLE blog (
  id INT,
  content VARCHAR(255),
  PRIMARY KEY(id)
);
CREATE TABLE tag (
  id INT,
  blog_id INT,
  name VARCHAR(100),
  PRIMARY KEY(id),
  FOREIGN KEY (blog_id) REFERENCES blog(id)
);

generateConfig.xmlのtableにtagテーブルも追加して、"Generate"!ってやってみたら、Tag関連のファイルは生成されましたらが、参照関係についてのフィールドなどは全然生成されなかったです。本来であれば、Blog.getTags()みたいなメソッドが追加されてList<Blog>を取得できるようなコード生成してくれることを期待したのですが、外部キーの生成は自動的にやってくれないらしいですね。これは手動でやるとMapperなどの修正も必要なので結構めんどくさく、非常に残念。。
こちらのフォーラムにも同様の記事がありました。。。これもまたプラグインを使って作ってみるしかなさそうですね。。

これらMyBatis Generatorの詳細はこちらのチュートリアルを参照ください。

オープンソースORMのMyBatis(旧iBATIS)を使う

オープンソースのORM(Object-Relational Mapping)で、結構日本では利用されているiBATISの後継であるMyBatisについて触ってみました。

MyBatisはiBATISがApacheファンデーションからスピンアウトして新しくフォークされたiBATISの後継フレームワークであり、バージョンで言うとiBATIS3.0という位置づけらしいです。ライセンスはApache Licenseで自由かつ無償にて利用することができます。現在はGoogle Codeでソースコードが管理されているようです。
http://code.google.com/p/mybatis/

MyBatis/iBATISは、JPA(Java Persistence API)ベースのHibernateOracle Toplinkなどと比べ、SQL文を中心に作成していくということもあり、企業システムなどではSpringなどと組み合わせて多用されているようです。(ちなみにSpringは3.0以上をサポートしているらしいです。こちらを参照。Spring2.xであればiBATISを使う必要がありそうですね。)


ちなみに日本では上記のようなトレンドですが、全世界では下図のように圧倒的にHibernateの方が人気があるようです。

では早速試してみたいと思いますが、ここではEclipse4.2とMySQLデータベースをあらかじめインストールされている前提で進めていきたいと思います。ちなみにデータベースとテーブルはシンプルすぎますが、以下のものを利用します。

CREATE DATABASE mydb;

CREATE TABLE blog (
  id INT NOT NULL,
  content VARCHAR(255)
);

MyBatisセットアップ
ここから"Core Framework"をダウンロードして、解凍します。

mybatis-x.x.x.jar(私は3.1.1を利用)とlibフォルダに入っているjarファイルをすべてEclipse上の自分で作成したプロジェクトにコピーしてビルドパスに設定します。さらにMySQLのJDBCドライバーであるConnector/Jをダウンロードして、同様にコピーしてビルドパスに設定します。
次にMyBatis設定ファイルであるmybatis-config.xmlファイルを作成します。このファイルはデータベースの基本的な接続情報と各Mapperファイル(単純に言えばSQL文の書かれているファイル)への外部リンクURLで構成されています。



  
    
      
      
        
        
        
        
      
      
    
  
  
    
  


コーディング
MyBatis設定ファイルで各Mapperファイルはmapperタグに外部ファイルとして指定します。通常は1テーブル(エンティティ)単位で作成することになると思います。そのblogテーブル(エンティティ)用のMapperファイルであるBlogMapper.xmlは以下のようにSQL文を書くことでBlogクラス(エンティティクラス)と紐付けます。


  
  
  
  
      insert into Blog (id, content) values (#{id}, #{content})
  
  
      delete from Blog;
  


このblogテーブル用のBlogクラスは以下のようになります。このクラスは通常のPOJOで書くことができます。
public class Blog {
 private int id;
 private String content;
 
 public int getId() {
  return id;
 }
 public void setId(int id) {
  this.id = id;
 }
 public String getContent() {
  return content;
 }
 public void setContent(String content) {
  this.content = content;
 }
 
 public Blog(int id, String content) {
  this.id = id;
  this.content = content;
 }
 
 public Blog() {
  super();
 }
}

以下はこれらBlogクラスとMapperファイルを利用してデータベースアクセスするサンプルで、mybatis-config.xmlとBlogMapper.xmlを読み込み、Blogクラス経由でblogテーブルにアクセスするコードです。


String resource ="mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession session = sessionFactory.openSession();
// delete records
session.delete("deleteAllBlogs");

// insert records
session.insert("insertBlog", new Blog(0, "Test1"));
session.insert("insertBlog", new Blog(1, "Test2"));

// select records
Blog blog = (Blog) session.selectOne("selectBlog", 1); 
System.out.println(blog);  
for (Object v : session.selectList("selectAllBlogs")) {
 System.out.println((Blog) v);
}
session.close();


詳細は、こちらのチュートリアルを参照ください。

次回は、これらのテーブル情報からエンティティクラスやMapperファイル(クラス)を自動的に生成するMyBatis Generatorについて説明したいと思います。



2012年11月6日火曜日

JavaでiPhone/iPadアプリを開発できる「Codename One」

JavaでiPhone/iPad、Androidなどのモバイルアプリケーションを開発できるというツール「Codename One」が、JavaOne 2012で取り上げられていたみたいなので、ちょっと調べてみました。

これはCodename One社のオープンソースプロジェクトで、Javaで書いたコードが、iOSやAndroid、BlackBerryのネイティブコードに変換されて動作する無償のツールみたいです。ツールの中身は、GUI BuilderなどのUIを作るツール、エディタなどのIDE、シミュレータ、Javaフレームワークとそのネイティブコードへの変換ビルダーなどがあるみたいです。(ビルドは使い込んでいくと課金対象のようです。)



有名な類似製品としては、JavaScriptでコードを書くという違いはありますがAppceleratorのTitaniumですね。

また、Macを持っていないユーザ用(?)にクラウドでビルドして、iOS用のインストーラであるipaファイルを生成できるようです。ただ、FAQを見ると、App Storeに上げるときなどに必要と言っているので、結局必要かな。。。と思いきや、ここで始めて知ったのですが、MacinCloudというMacOSのクラウドがあるらしいですね。Mac持っていない人には使えるかも。

話はそれましたが、要はJavaを知っていれば、クロスプラットフォームで稼働するモバイルアプリケーションが作れるっていうツールらしい。昔、TitaniumはAndroid/iOS上で同じコードでちゃんと動かなかったという経験(私の実装が悪かったのかも。。)があるので、こちらには期待したいですね。

アプリ開発の流れ
EclipseかNetBeansで開発することになるようです。

新規プロジェクト作成をすると以下の3つのソースコードが生成されるようです。
  • StateMachineBase: GUI Builderで自動生成された画面のクラスです。このファイルは手動でいじらないようです。
  • StateMachine: StateMachineBaseを継承したクラスで、StateMachineBaseを操作するようなコードをここに書くようですね。例えば、GUI Builderで作ったボタンの押下イベント発生時の処理をメソッドとしてコーディングするとか、あるイベントが発生したときに、GUIのデータセットなどを行うとか、ほとんどの処理はここに書いていくことになりそうです。
  • Main: まぁ、メインですね。AndroidやiOSのフレームワーク同様、ライフサイクルを持っているようです。

ソースコードに加え、以下のようなリソースファイルも。
  • レイアウトデザイン/テーマ/イメージ
  • 国際化
  • データファイル
APIも充実していて、以下のようにセンサーなどのモバイル特有のものにもアクセスが可能です。

  • カメラ
  • GPS
  • ビデオ
  • ストレージ
  • ネットワーク
  • サーバプッシュ
  • メール/SMS/電話
  • JSON/XML
  • 地図 など

稼働確認するには独自のシミュレータで実行することもできますし、クラウド上でビルドして、ビルド完了すると、作成された.ipaファイルのURLをメールなどで受け取り、iPhone/iPadなどの実機でダウンロードして実行できるようです。(Androidのシミュレータは非常に遅いので、これは速いといいのですが。)

まだ、実際にはダウンロードして試していませんが、ネイティブコードに変換されることからPhoneGapなどのWebkitベースのツールより高速に動くと思いますし、Objective-Cはイマイチ慣れないので、作りたいアプリが思いついたら試してみたいと思います。

※若干気になる点はGalleryにApp Storeに公開されているアプリがなさそう。。。まぁ、これからですかね。


2012年11月1日木曜日

Node.jsが面白い件①

別リンクのGoogle Cloud Messaging for Android(GCM)を試してみるで書かれているように、サーバプッシュ技術ということでNode.js+socket.ioあたりのことを書こうと思ってたんですが、ちょっと寄り道してNode.jsについて書いていこうと思います。node.jsすげぇー!(javascriptで全部書けるとか、v8が早いとか、スケールプログラムが書きやすいとか)の解説は他のサイトに譲るとして、ここでは簡単なプログラムを作っていろいろ試して行こうと思います。超簡単な以下のようなものをNode.jsでつくってみることで、アプリケーションに必要な各要素を実現してみます。


この図のそれぞれ黄色の部分、
  1.  データベースアクセスはどうするの?
  2. ログはどうなってるの?
  3. ルーティングってどうやってるの?
  4. セッション関係は?
  5. 認証ってどうやってるの?
という基本的なところをまずは作っていきます。Node.js初心者の方もNode.js熟練者の方も、「普通こうやるだろ」や「こんなときどうするの」などのコメントを自由にもらえると嬉しいです。では、次回に続きます!