だいたいOrmaでAcitiveAndroid (AA) を置き換える準備が整ったので、手順を書いておきます。
Table of Contents
Step 1 - インストール
build.gradleのdependenciesを書き換えます。最新版はOrmaの公式サイトで確認してください。
- compile 'com.michaelpardo:activeandroid:+' + apt 'com.github.gfx.android.orma:orma-processor:2.+' + compile 'com.github.gfx.android.orma:orma:2.+'
Step 2 - スキーマ定義
以下のことを念頭に置いてModelの @Table
と @Column
を書き換えます。
- AAの
@Table(name = "...")
は Ormaでは@Table("...")
または@Table(value = "...")
と書く。ただしテーブル名がクラス名と一致するなら名前は不要。なおOrmaはリフレクションを使わないので、ProGuardの影響をうけません - AAの
@Column(name = "...")
は Ormaでは@Column("...")
または@Column(value = "...")
と書く。ただしカラム名がフィールド名と一致するなら名前は不要 - AAの
@Column(... unique = true, onUniqueConflict = Column.ConflictAction.REPLACE)
は Ormaでは@Column(... uniqueOnConflict = OnConflict.REPLACE)
と書く - AAは
Id INTEGER PRIMARY KEY
カラムを暗黙のうちに定義するが、Ormaはすべて明示しなければならない。つまり@PrimaryKey long id;
が必要。なおSQLiteでは名前は大文字・小文字を区別しないのでid
とId
は同じ意味 - Ormaでprimary keyでソートしたい場合は
@PrimaryKey(autoincrement = true)
が必須。これは、そもそもautoincrementでないprimary keyは順番に意味を持たないため*1 - Ormaでカラムを検索したりソートしたり場合は、
@Column(... indexed = true)
が必須。これは、INDEXのないカラムで検索やソートをすべきでないため - AAだとあらゆるカラムがNULL制約なし(つまりnullable)で定義されるが、OrmaではデフォルトでNOT NULL制約がつく。DBのカラムをnullableにしたいときはスキーマクラスのフィールドに
@Nullable
注釈をつけること
ここまで作業したら一旦ビルドを走らせてOrmaDatabaseやヘルパークラスを生成します。コンパイルエラーが残っていても、スキーマ定義があればヘルパークラスは生成されると思います。
Step 3 - 初期化
OrmaDatabaseインスタンスはアプリケーション単位でシングルトンにするといいでしょう。以下は初期化のためのテンプレです。なおデバッグビルドだとかなり詳細にログがでますが、リリースビルドでは自動的にログは抑制されます。
また、Ormaにはメインスレッドでの読み込みで警告を、書き込みで例外を発生させる機能があります。しかしAAを既に使っているアプリだとその制限が強すぎるので、弱めるほうがいいでしょう。
public static OrmaDatabase createOrmaInstance(@NonNull Context context, @Nullable String name) { return OrmaDatabase.builder(context) .name(name) .migrationEngineStep(3, new ManualStepMigration.ChangeStep() { @Override public void change(@NonNull ManualStepMigration.Helper helper) { // AAのmigration file (e.g. 3.sql) の中身のうち、必要なものだけを書く // CREATE TABLEは自動で行われるので、「データが消えてもいいので単にテーブルを作り直したい」というテーブルは消すだけでよい helper.execSQL("DROP TABLE IF EXISTS FooBarLog"); } }); .typeAdapters(typeAdapters) .readOnMainThread(AccessThreadConstraint.NONE) .writeOnMainThread(BuildConfig.DEBUG ? AccessThreadConstraint.WARNING : AccessThreadConstraint.NONE) .build(); }
AAのserializerを使っている場合、Ormaではtype adapterが必要です。type adapterについてはREADMEをご覧ください。
https://github.com/gfx/Android-Orma#static-type-adapters
Step 4 - CRUD操作
アノテーションプロセッシングで生成される ${モデル名}_Relation
というヘルパークラスを、モデルごとのCRUD操作のエントリポイントにできます。Relationクラス自体は、あるテーブルに対してフィルタ条件とソート条件を設定可能なオブジェクトです。
たとえば、以下のようなTodoクラスがあるとき:
@Table public class Todo { @PrimaryKey(autoincrement = true) public long id; @Column(indexed = true) public String title; @Column @Nullable public String content; @Column(indexed = true) public boolean done; @Column public Timestamp createdTime; }
レコードの生成順と逆順でRecyclerViewに表示するのであれば、次のようにRelationインスタンスを生成し、それを通じてモデルを操作することになるでしょう。
@Table public class Todo { // ... public static Todo_Relation relation() { OrmaDatabase orma = OrmaHolder.getOrmaI(); // どこかにOrmaDatabaseのインスタンスがあるとする return orma.relationOfTodo().orderByIdDesc(); } }
あるモデルに関するすべての操作はRelationから行えます。詳細は OrmaのREAMDEより: GitHub - gfx/Android-Orma: A lightning-fast ORM for Android as a wrapper of SQLiteDatabase
AAと比較した注意点は以下のとおりです。
- AAと異なり、「INSERTまたはUPDATEを行う」という意味での
save()
はOrmaにはない。かわりに primary keyやuniqueに対するOnConflict.REPLACE
でほとんどの場合代替できる @PrimaryKey(auto = true)
となっていると(これがデフォルトの挙動)、INSERTでそのカラムは無視される。これを無視しないようにするには(つまりPKでINSERT OR UPDATE
するには)、Relation#upserter()
を使う
以上を踏まえると AAの save()
と delete()
はほとんどの場合、以下のようになります。いずれもモデル固有のヘルパークラスを経由するので、モデルの基底クラスに寄せることはできません。
public void save() { if (id == 0) { relation().inserter().execute(this); } else { relation().upserter().execute(this); } } public void delete() { relation().deleter().idEq(id).execute(); }
また、Relationインスタンスをうけとる RecyclerView.Adapter
のサブクラスである OrmaRecyclerViewAdapter
と、 BaseAdapter
のサブクラスである OrmaListAdapter
が提供されているので、RecyclerViewやListViewなどでRelationを直接表示できます。
使い方はexampleを参照のこと。
- Android-Orma/RecyclerViewActivity.java at master · gfx/Android-Orma · GitHub
- Android-Orma/ListViewActivity.java at master · gfx/Android-Orma · GitHub
その他注意点
- Ormaのmigrationは、カラムの追加・削除・型や制約の変更などは自動的に検知されてalter tableやテーブルの再生成が行われる。しかしmigrationはデータ量に応じた時間がかかるためテーブルによっては注意深く行うこと。migrationの発動を最小限に抑えるため、
@Column(storageType = "INTEGER")
などの設定を行える- 置きかえる段階ではリスクを最小限に抑えるため、テーブルを再構築するmigrationは発動しないようにするほうがいい
- 自動マイグレーションだけでなく、手動でステップを定義するマイグレーションも用意されている(
OrmaDatabase.Builder#migrationStep()
)。 - マイグレーションのログはデバッグビルドでかなり詳細に出るのでデバッグは比較的容易なはず
- 後々のカラムの追加では、必ず
Column#defaultExpr
を設定する必要がある。さもないと自動マイグレーション時にクラッシュする - OrmaはRobolectric・Android両方で動作する。テストのときはdatabase nameをnullにするとオンメモリDBになるので、テストケースごとのcleanupの手間を省ける
- Orma自身は Robolectric-Instrumentation によって双方の環境でテストされている
*1:autoincrementを指定するとINSERTが少し遅くなりますが、気にするほどではありません。