はじめに
はじめまして。ジモティーに2021年1月からAndroidアプリエンジニアとしてい働いている谷です。
今回はAndroidアプリエンジニアとしてローカルDBをSQLiteからRoomに置き換えた話をさせていただければと思います。
Roomとは
置き換えの話に入る前にさらっとRoomについて説明したいと思います。 RoomとはGoogleが推奨しているSQLiteのORMです。 SQLiteHelperの作成やCursorの操作などめんどくさい処理を一手に引き受けてくれます。
実装も非常にわかりやすく、3つのコンポーネントを作成するだけでローカルDBの構築が可能です。
- Entity
- Dao
- Database
合わせて非同期処理としてRxやKotlin Coroutinesなどのサポートもされており、非常に使いやすいです。
さらにデバッグツールとしてAndroid Studio 4.1から追加されたDatabase Inspectorというものがあり、APIレベル26以上の端末でアプリを実行している時に使用できます。 作成したテーブルの中身をSQLを使って操作したり、Database Inspectorのウィンドウでそのまま書き換えたりといったことが可能です。 こちらにどんな感じのものか詳しく書かれている記事がありますので、是非見てください。
そして以下簡易的ではありますがRoomの実装例です。
まずはEntityを作ります。 これがテーブルの役割を果たします。
@Entity(tableName = "tbl_todos") data class Todo( @PrimaryKey(autoGenerate = true) val id: Long, val title: String, val isActive: Boolean )
続いてDaoを作成します。 データを取得したい場合はこちらに定義したメソッドを呼び出すことになります。
@Dao interface TodoDao { @Insert suspend fun insertTodo(todo: Todo) @Query("SELECT id, title, is_done FROM tbl_todos") suspend fun getAll(): List<Todo> @Query("SELECT id, title, is_done FROM tbl_todos WHERE is_done = :isDone") suspend fun getDoneTodo(isDone: Boolean): List<Todo> @Update suspend fun updateTodo(todo: Todo) @Delete suspend fun deleteTodo(todo: Todo) }
最後にDBを実装します。
@Database(entities = [Todo::class], version = 1, exportSchema = false) abstract class TodoDatabase : RoomDatabase() { abstract fun getTodoDao(): TodoDao companion object { private const val DATABASE_NAME = "todo-db" fun createTodoDatabase(context: Context): TodoDatabase { return Room .databaseBuilder( context.applicationContext, TodoDatabase::class.java, DATABASE_NAME ).build() } } }
おそらく基本的にはDaggerなどでインジェクションすると思いますが、以下のように呼び出すことが可能です。
// Daoの取得 val todoDao = TodoDatabase.createTodoDatabase(context).getTodoDao() // 取得したDaoから各種メソッドでローカルDBにアクセスする todoDao.insertTodo(Todo("保存したいTodoのタイトル"))
ここまでが基本的なRoomの使い方となります。
背景
元々ジモティーのAndroidアプリではローカルDBとしてSQLiteとRealmが採用されておりました。 ジモティー内での使い分けとしては以下のような感じです。
- SQLite
- サーバーから取得したマスターデータをキャッシュするために使用。
- Realm
- 検索結果の履歴保存などに使用。
上記の内、SQLite部分で
- 実装されているコードが古い。
- 可読性が低くマイグレーションなどが発生した際にメンテナンスしづらい。
- ローカルDBへの接続処理がUIスレッド上で実装されている。
という負があり、その辺りが解消されるのではないかということで導入することにしました。
以下、置き換え実施時に参考にした情報です。
- Migrate from SQLite to Room
- SQLiteからRoomへのマイグレーション方法について基本的なことが書かれており非常に分かりやすかったです。
- Incrementally migrate from SQLite to Room
- 後述するのですが、今回は完全な置き換えができなかったので段階的に置き換えをするにはどうしたら良いかが書かれておりこちらも非常に有用でした。
結果
結果は完全な置き換えはできず、段階的な置き換えを実施する形になりました。 修正範囲が当初想定していたよりも広くなってしまったのと、アーキテクチャに則って作られていない部分の改修難易度が高く時間的にも厳しかったためこの判断になりました。
完全な置き換えはできなかったのですが、最終的には以下が実施できました。
- 既存テーブルをRoomのEntityとして再定義
- Stringの配列で設定されていた既存のテーブルがKotlinのdata classとなったことで可読性が上がりました。
- SQLiteを呼び出していた箇所の一部RoomのDao化
- Dao内に定義しているQueryアノテーションでSQLが書けるので何をしているのかすぐに把握できるようになった。
- さらにRoomには決まった形で使われる挿入や更新、削除はコンビニエンスクエリとして事前に定義されたアノテーションが存在するためSQLを書く手間が省け、小さいですが実装時の工数削減になりました。
- RoomのDaoに置き換えられた箇所では、ジモティーではRxをまだ使用しているのでSingleを返却するようにでき非同期実行できるようになりました。(アプリ全体としてKotlin Coroutinesへの移行を行っているのでどこかのタイミングでsuspend functionに変更する予定です。)
- SQLiteからRoomへの置き換え時に発生するマイグレーション処理の追加
- Migrationを使うことで各スキーマバージョン単位でマイグレーション処理を記述することができるため、処理が追いやすく、メンテナンスしやすい状態になりました。
- SQLiteOpenHelperをSupportSQLiteOpenHelperに置き換え
- これにより既存のSQLiteの実装を壊すことなく、Room側のSQLiteに移行することができ、今後他の箇所のDao移行も行えるようになりました。
まとめ
今回移行してみて、置き換えたことにより非常に可読性が高く、UIスレッドをブロックしないように作れるためパフォーマンスも良いアプリになっていくだろうと思っております。
今回置き換えを行っていて、途中で方針転換をしないといけなくなったり、リリース直後にマイグレーション周りで不具合を出してしまったりと進め方として改善すべき点も見えてきた経験でした。
今後はSQLiteの処理からRoomのDaoに置き換えることができていない部分がまだまだあるので、そちらも随時進めて行きたいです。
弊社では一緒にプロダクトを改善していただける仲間を探しています!
こちらでお気軽にお声がけください!