Edited at
AndroidDay 5

天下一「AndroidのORM」武道会(2014年版)

More than 3 years have passed since last update.


はじめに

この記事は「Android Advent Calendar 2014」の5日目の記事です。


追記(2015/12/19)

2015年版を書きました。どうぞご覧下さいませ。


tl;dr


AndroidのORM事情

スマートフォンアプリでは、ネットワークから取得したデータの保持や、ユーザーが入力したデータの保持、その他いろいろなデータの管理にSQLiteデータベースを使用することが多いと思われます。

これらはAndroidの標準APIで操作できますが ―一度やってみればわかりますが― 非常にめんどくさく、思わず険しい顔になってしまいます。

そんなAndroidのSQLite操作を楽に行うため(そして実装時間節約のため)、O/Rマッパー(ORM)を導入するのは良い判断だと思います。

ところが、AndroidのORMは群雄割拠で、どれを選んだらいいのかわからないという問題があります。

あと、「○○が良い」「いや○○が今主流」という話が風のうわさで流れてくるのですが、実際のところどうなの???というのが分からなかったりします。

どのORMを使ったら良いのか、という問題は、個人的に大疑問だったのですが、今回Advent Calendarをやるということで良い機会でしたので調べることにしました。

題して、【天下一「AndroidのORM」武道会】!!!


趣旨

群雄割拠のAndroidのORMにひとつの解を見つけ出すため、性能を比較して議論に終止符を打つ(あるいは更なる混沌へと誘う)ことを目的とします。


エントリーリスト

エントリーリストの選定基準として、「古くからあるもの」「最近名前をよく聞くもの」「NoSQL」のどれかに判定されるものから個人的嗜好で選択しました。

基本的に、定期的な更新が行われていないものは、よほど有名でない限りは除外することとにしました。

そんなわけで選ばれたのは以下のORMです。build.gradleに記述したライブラリ情報も掲載してあります。


1. ORMLite

古参枠。

もともとJava向けのORMなのですが、公式のAndroid用ライブラリと組み合わせることでAndroidでも使えます。

SQLiteOpenHelperを継承したクラスを用意する必要があるものの、Utilメソッドのおかげで楽にテーブル作成ができたりと、柔軟性が高い印象です。

ドキュメントも充実していて、StackOverflowでは作者自ら返答しており、困ったことはググればだいたい解決できる印象です。

compile 'com.j256.ormlite:ormlite-android:4.48'


2. ActiveAndroid

古参枠。

ActiveAndroidはリフレクションを巧みに使うことで、本来は宣言すべきテーブル作成関連の記述をする必要のない、お手軽なORMです。

(今では割と一般的なアプローチなのですが、「なんでこんなことが出来るんだ?」と不思議でした。)

僕の観測範囲では非常に高い人気がありますが、hotchemiさんの日本語訳(ActiveAndroidのWikiを和訳した - Qiita)のおかげで国内での敷居が下がったのも要因ではないかと思っています。

今回はsonatype版を使用しました。(リリース版で比較すべきだとは思うので、ちょっとアンフェアかもしれないけど、jarを配置してセットアップするのはなんとなく嫌だった)

また、rejasupotaroさんの記事(ActiveAndroidの初期化時間を4分の1にする - Rejasupoem)にある「ActiveAndroid#initialize(Configuration) で初期化」を使用しています。

これは高速化の意図は特になく、ART環境の特定条件下でクラス一覧を取得するときに予期せずクラッシュする問題を回避するためです。

(なお、SNAPSHOT版ではAA_MODELSというメタデータをAndroidManifestに記述することで、Javaでクラスを記述するのと同様の効果が得られます。)

compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'


3. greenDAO

古参枠。

何よりもパフォーマンスが良い(公式サイトにはORMLiteとの比較が掲載されています)ことと、コードジェネレーターでModelクラスを生成するのが特徴的なORMです。

個人的に、ORMにもかかわらずコード生成することが面倒な印象だったのですが、良い機会だったので選定しました。

compile 'de.greenrobot:greendao:1.3.7'

コードジェネレータ部分のbuild.gradleはgithubにて公開してあります。

(Javaで書くの辛かったのでGroovyで書きました。)


4. Ollie

期待の新星枠。

ActiveAndroidの作者の新作です。導入方法を見る限りアノテーションを使ったAPT生成っぽい感じです。

ActiveAndroidのリフレクションの遅さが改善されているかどうか?という点が非常に期待できそうですが・・・。

結論を先に述べると、以下のprovided指定を攻略することができずに、テストどころか試用することも叶いませんでした。

EclipseだとAnnotationProcessing設定をすれば動いたという報告があったのですが、いまさらEclipseも微妙感あったので試してません。

ただ、せっかく選定したので、グラフなどにはOllieが残り続けています。

provided 'com.michaelpardo:ollie-compiler:0.2.0'

compile 'com.michaelpardo:ollie:0.2.0'


5. SugarORM

期待の新星枠。

ActiveAndroidを更にシンプルにしたような感じです。拡張性は低いですが、ハマればかなり素早く実装できそうです。

Ollieが評価できなかったので、代打として選定しました。

compile 'com.github.satyan:sugar:1.3'


6. DBFlow

期待の新星枠。

2014年9月頃にいきなり現れた超新星です。AndroidのためのORMという感じで、フルスペックのAPIセットは非常にアツいと思います。

ModelView(AndroidのViewコンポーネント)、Observable Model(モデルの変更通知を行う)があります。

また、ActiveAndroidやOllieなどのORMを参考に、機能と拡張性と提供しながら最適なものを選択するようにしている、とREADMEに書いてありました。

APT生成を行うため、gradleにandroid-aptプラグインを導入する必要があります。

apt 'com.github.agrosner:DBFlow-Compiler:1.0.1'

compile 'com.github.agrosner:DBFlow-Core:1.0.1'
compile 'com.github.agrosner:DBFlow:1.0.1'


7. Realm

NoSQL枠。

SQLiteやCoreDataを置き換える新手のモバイル向けデータベースです。

SQLiteを使っていない(独自のバイナリ形式)ためSQL文を投げることはできないですが、インターフェースを見る限りでは、そこはさほど問題にならない印象。

それ以外にもAndroid向けにあると嬉しいAPIセットがそれなりに搭載されています。

compile 'io.realm:realm-android:0.73.1'


8. couchbase lite

NoSQL枠。

もうひとつくらいNoSQL枠にエントリーさせたかったのでヒアリングしたところ、名前が挙がったので登録。

検証していてわかった衝撃の事実もあるのですが、後述します。

Sync gateway機能は今回は使っていませんが、同期処理が楽になりそうな印象もあります。使ってないのでわかりませんが。。。

compile 'com.couchbase.lite:couchbase-lite-android:1.0.3.1'


検証方法


  • 「Simple」クラスを作成し、このクラスのプロパティをSQLite(NoSQL DB)にINSERTおよびSELECTする


    • クラスの中身は下記掲載の画像を参照(StringValueはString型、ShortValueはshort型・・・という設定)



  • AndroidTestCaseを継承したユニットテスト環境で動かす。

  • INSERTは、10,000件のレコード挿入を行う


    • Bulk Insert(transactionで括って早くする)を使用した場合、しなかった場合の2通りを検証する



  • SELECTは、booleanValueがtrueのものだけを返すようなSELECT文(に準拠する命令)を与え、5,000件帰ってきているかどうかをassertにかける。

  • INSERTとSELECTにかかった時間を、TimingLoggerクラスを少し修正したものを使用し、それぞれ計測。Logcatに出力される数値をスプレッドシートに記録する。

  • 実行後、DBが残ってしまうので、データベースファイルをmoveするか、テーブルのデータを全部削除するコマンドをtearDown()で投入する。

  • 計測を5回繰り返し、終わったら adb uninstall hoge.fuga.foo.bar; adb uninstall hoge.fuga.foo.bar.test する。

  • ライブラリはそれぞれ以下の順番で捜索し、存在したものを使用する。


    • jcenter(maven)にあるか

    • sonatypeにあるか

    • 独自提供のmaven reposにあるか

    • 公式サイトやgithubにjarがあるか



  • 検証機は「Xperia A SO-04E Android 4.2.2」を使用する。


    • 自作アプリで一番利用者が多いのがこれだったから選定

    • 検証中の端末モードは「機内モード」とし、終了可能なバックグラウンドタスクやサービスは終了させる



まとめると。


  1. AndroidStudioから各ユニットテストクラスを実行する

  2. しばらく待つ

  3. テストがパスされてLogcatに計測結果が出る

  4. スプレッドシートに結果を貼り付ける

  5. 1に戻る。合計5回やる。

  6. 5回繰り返したらadb uninstallし、次のテストケースへ。(1に戻る)


懸念点

上記の検証方法では、以下の点で公平さを欠くと考えています。


  • ライブラリの性質などの都合、必ずしもテスト実行方法が統一されていない


    • 例えばSugarORMは現行リリース版ではApplicationクラスに依存するため、Applicationクラスをテストするためのクラスを使う必要があった



  • ライブラリの性質などの都合、tearDown()のやり方が統一されていない


    • 例えばSugarORMはApplicationクラスの生成時にデータベースの初期化をする(逆に言うとそれ以外ではできない)



つまり何が言いたいかというと、「この検証結果によって生じたいかなる問題についても筆者は一切の責任を負いかねます」。

# ただし、より良くしたいとは思っているので、優しく指摘いただけると嬉しいです。


検証用コード

検証のために使用したソースコードは以下から入手できます。


検証結果

検証結果は以下のGoogleスプレッドシートから確認できます。


大会総括

正直なところ、僕はSQLiteやJavaに詳しくないので、自分でコード書いた感想や、動かして得た結果への感想のみを述べさせていただきます。


数値から総括する


INSERTについて



(横軸の目盛最大値が異なる点に注意してください)

Realmは速いですね。greenDAOはSQLite陣営では最速を出しています。SQLiteは遅いものだ、という認識があったのですが、実装次第なのかなぁ、とも思いました。

ActiveAndroidは予想通り遅かったです。ただSugarORMも似たような作りだと思うのですが、INSERTに優位な差があるのが不思議(コード読んで比較してません!)。

大量のデータを入れるときにTransactionでまとめると(グラフ内の「BulkMode On」の時)、10倍以上パフォーマンスがよくなるケースがありました。

これはORMに限らずSQLite全般に言える話ですが、意外と見過ごされているかも知れません。

とはいえ何でもTransactionで纏めるのもどうか、、、という小学生並みの感想はあります。


SELECTについて

Realmはグラフに描画した時に線が見えないくらい脅威のパフォーマンスを発揮しました。これがNoSQL DBなのか・・・!

あまりにも速すぎたので、RealmResultsを拡張for文で回して単なるArrayListに戻す処理を入れたものも計測したのですが、それでもgreenDAOより僅かに遅いくらいのレベルです。

逆に、couchbase liteの性能の悪さが目立ちます。テスト計測中に10分ほど居眠りして、目覚めたらまだテスト回っててビビりました。

自分の無理解故の可能性もあり改善可能な気もするのですが、無理じゃないか?と思う理由として、

couchbase liteはcouchbaseっぽいインターフェースをもったSQLiteデータベースだということが、テスト結果取得後に分かりました。

(ボトルネックになっている箇所をMethod Trackingで確認したら、SQLite関連のクラスで時間を食っていたという・・・。)

ただ、じゃあcouchbase liteが無能かというとそうではなくて、このライブラリはサーバー側のcouchbaseからデータベースを取得できる「Sync gateway」という特色を持っているようです。

今更言うな感はあるのですが、「その極端な(実際の用法では有り得ないような実験による)結果で何が言えるの?」というのはあります。

あくまで数値を出すことは一つの切り口なので、この結果のベストが優れている、ワーストが劣っているという見方は微妙です。

例えばDBFlowはBaseModel#save(boolean)で書き込みを行いますが、引数を渡すことができます。この引数は、非同期で処理を行うかどうかを表しており、trueに指定すれば数値結果は良くなるはずです。しかし今回の検証では、この値をfalseにしてあります。

よって、実環境では性能(というよりも使い勝手)が逆転するケースもあると思います。


greenDAOについて

案の定、コードジェネレータによる自動生成は面倒だなぁ、と思いましたが、その性能を考えると充分回収可能ではないかと思いました。

そして、コードジェネレータ部分のコードは、別にJavaで書かなくてもOKです。AndroidStudioなら少々build.gradleに記述を足すだけでGroovyで書くことが出来ます。

あと、コードジェネレータではProtocolBuffer用の生成を行うことができるようです(むしろコードジェネレータで提供しないとprotobufをサポートできないということか・・・?)。


Ollieについて

実は選定段階では一番楽しみなORMだったのですが、provided指定が何のことか分からず(aptかな?と思ってandroid-aptと組み合わせたけれどエラー・・・)。

どなたかAndroidStudioでの倒し方知ってたら教えて下さい。。。


SugarORMについて

そんなOllieの代わりに評価したSugarORMですが、ActiveAndroidよりシンプルになっている印象です。

あまりフルスタックなORMではないですが、性能もActiveAndroidより良いので、個人アプリ製作などで用途が限定されているならば優れているORMかも知れません。

また、類似するActiveAndroidとの性能を比較すると興味深いです。INSERTはSugarORMのほうが良い結果を出していますが、SELECTはActiveAndroidとあまり差がない(誤差レベル、と掃き捨てるには少し差があるようにも思いますが。。。)ように感じます。


DBFlowについて

INSERTの結果は芳しくないですが、このORMはAndroidの為の機能がふんだんに盛り込まれており非常に面白いORMです。

ModelViewなどを使えば、ModelとViewを簡単に連携させることができそうですし、Observable Modelsではモデルの変更があったときにコールバックを叩いてくれたり。

また現時点(2014/12/05時点)でまだ活発に開発が続けられていることも高評価です。

(ただ見た限り、byte[]に対応してないように見受けられるので、そのあたりを使う人は気をつけた方が良さそう)


Realmについて

Realmは、SQLiteなORMとの対比をさせると面白いかなぁ、と思って選定したのですが、予想以上の数値結果を叩きだしたので驚きました。特にSELECTの速さは異常です。

そういった性能の良さだけではなく、ListViewなどで使えるBaseAdapterクラスを提供していたり機能面も充実しています。

ただ、マイグレーションやEncryption周辺はWIP状態であり、また現状ではそんなに使いやすいものでもないようです。

とはいえ、Production利用は2012年からやってるぜ、みたいなFAQが書いてあったりするので、本番アプリに導入しても良いレベルかも知れません。


couchbase liteについて

NoSQL枠がひとつだけというのも寂しいので探していたところ、Twitter経由で教えて頂いたこともあり選定リストに加えました。

ただ、先に述べたとおり、これはJSONライクなドキュメント型でデータ操作できるようにしたSQLiteのラッパー、という感じで、速さを期待する物ではないように思いました。

私見ですが、ドキュメント型の概念もJavaそのものとのとっつきの悪さが目立ちます。

JSONだと hoge.fuga.foo とかやって値にアクセス出来るものが、このライブラリでそれをやろうとすると、hoge.getProperty("fuga").getProperty("foo")という感じになってしまいます。。。

どうも僕からすると、「JSONを投げ込んだらUserインスタンスが出来上がったぞ〜」というのを期待するので、ちょっと微妙な感じなんですよね。。。

ただ、もしかすると日常的にcouchbaseを使っていればわかりやすい方法で、Sync gatewayと併せればサイコーで、

「こうアクセス出来る方が都合がいいんだ!」ということなのかな。。。とか思ったりもします。。。よく分かりません。


その他


  • ART環境だと結果が変動するかもしれない


    • リフレクションを使った時のメソッド応答時間がDalvikと比較して変化してるらしい



  • Modelを内包する(idを持つ)ModelをSELECTしたときのパフォーマンス計測ができていないのが心残り


    • Lazy Loading / Eager Loading の違いとか




3行でまとめ


  1. Realmめっちゃ早かった

  2. SQLiteだけで見ればgreenDAO早かった

  3. でも速度だけがORMの良さを決めるってものでもない

こちらからは以上です。

引き続きAndroid Advent Calendarをお楽しみ下さい!