Room Persistence Libraryを使用してデータを永続化しているのだが、Ruby on Railsのcreated_atのようなタイムスタンプを付ける必要があった。スマートな方法ではないものの、一応実現する方法を考えたのでメモっておく。
問題
公式ドキュメント通りに実装すると、DaoとDatabaseは次のような感じで定義することになると思う。
@Dao
interface UserDao {
@Insert
fun insert(user: User)
}
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
UserDao
のinsert
は実装を定義できないので、このままだとDaoを使用する側でinsertする前にcreated_atを設定してやらなければならない。たぶん次のような感じになるのかな。
db.userDao().also { dao ->
...
user.createdAt = LocalDateTime.now()
dao.insert(user)
}
これだとDaoを使う側で毎回タイムスタンプを設定してやらなければならないし、何より設定漏れが発生する可能性がある。できれば、railsみたいに自動で設定してほしいところ。
解決方法
Dao#insert
をフックできれば早いんだけど今の所そういったことはできないみたい・・・。仕方ないのでDaoのラッパーを作ってやって、常にラッパー経由でデータベースにアクセスするようにしてやる。
まずはDatabaseを変更して、自動生成されるDao_Implではなくラッパークラスのインスタンスを返すようにする。
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
protected abstract fun getUserDao(): UserDao
fun userDao(): UserDao = UserDao.Wrapper(getUserDao())
}
UserDao
を返すabstractメソッドはprotectedにして外部からのアクセスを禁止する。代わりにuserDao
メソッドを公開して、こいつでWrapperクラスのインスタンスを返すようにする。
続いて、UserDaoの定義は次のような感じ。
@Dao
interface UserDao {
@Insert
fun insert(user: User)
class Wrapper(private val dao: UserDao) : UserDao {
override fun insert(user: User) {
user.createdAt = LocalDateTime.now()
dao.insert(user)
}
}
}
こうしておけば、常にDaoのWrapper経由でデータベースにアクセスすることになるので、createdAtの設定漏れは発生しない。
自動生成Dao_Implをコンポジションするので、タイムスタンプ周りの用件に限らず、色々と使いみちがあると思う。
※ 本記事ではcreatedAtにLocalDateTimeを使用していますが、TypeConverterに関する内容は省略しています