[Android Architecture Components] Roomでテーブル継承を扱う

この記事は[SmartDrive Advent Calendar 2017](https://qiita.com/advent-calendar/2017/smartdrive) 24日目の記事です。

Android Architecture Componentsの[Room](https://developer.android.com/training/data-storage/room/index.html)を触る中、テーブル継承を扱う場合に結構特徴が現れるなぁと思ったのでそのあたりを紹介していきます。

# はじめに
テーブル継承についてはこの辺を参考にしてください。
http://bliki-ja.github.io/pofeaa/SingleTableInheritance/
http://bliki-ja.github.io/pofeaa/ClassTableInheritance/
http://bliki-ja.github.io/pofeaa/ConcreteTableInheritance/

また、ここでは各種テーブル継承のpros/consのお話はしません。
Roomを使った実装について焦点を当てていきます。

# Model
```kotlin
sealed class Data {
    abstract val id: String
    abstract val name: String
}

data class DataA(override val id: String, override val name: String, val a: String) : Data()
data class DataB(override val id: String, override val name: String, val b: Int) : Data()
```

このような継承関係にあるModelを単一テーブル継承/クラステーブル継承/具象テーブル継承それぞれのEntityとDaoを書いてみます。


# 単一テーブル継承

```kotlin
@Entity
class DataEntity(
        @PrimaryKey
        val id: String,
        val name: String,
        val a: String?,
        val b: Int?
)

@Dao
interface DataDao {
    @Insert
    fun insert(vararg dataEntity: DataEntity)

    @Query("SELECT id, name, a FROM dataEntity WHERE name = :name")
    fun findDataAByName(name: String): List<DataA>

    @Query("SELECT id, name, b FROM dataEntity WHERE name = :name")
    fun findDataBByName(name: String): List<DataB>
}
```

単一テーブル継承でこのように実装してみました。
ここで注目したいのはModelのNotNullなfieldとDataEntityのNullableなfieldです。
他のORMだと`!!`をつけたり無駄にnullチェックをする必要があったりしてなんだかなぁという気持ちになるのですが、RoomではQueryの結果をPOJOにmappingする機能のおかげでNotNullとNullableのミスマッチをあまり意識することなくコードを記述することができます。

# クラステーブル継承
```kotlin
@Entity
class DataEntity(
        @PrimaryKey
        val id: String,
        val name: String
)

@Entity(foreignKeys = [(ForeignKey(entity = DataEntity::class, parentColumns = ["id"], childColumns = ["dataId"]))])
class DataAEntity(
        @PrimaryKey
        val dataId: String,
        val a: String
)

@Entity(foreignKeys = [(ForeignKey(entity = DataEntity::class, parentColumns = ["id"], childColumns = ["dataId"]))])
class DataBEntity(
        @PrimaryKey
        val dataId: String,
        val b: Int
)

@Dao
interface DataDao {
    @Insert
    fun insert(vararg dataEntity: DataEntity)

    @Insert
    fun insert(vararg dataEntity: DataAEntity)

    @Insert
    fun insert(vararg dataEntity: DataBEntity)

    @Query("SELECT dataEntity.id AS id, dataEntity.name AS name, dataAEntity.a AS a FROM dataEntity, dataAEntity WHERE dataEntity.name = :name AND dataEntity.id = dataAEntity.dataId")
    fun findDataAByName(name: String): List<DataA>

    @Query("SELECT dataEntity.id AS id, dataEntity.name AS name, dataBEntity.b AS b FROM dataEntity, dataBEntity WHERE dataEntity.name = :name AND dataEntity.id = dataBEntity.dataId")
    fun findDataBByName(name: String): List<DataB>

}
```

クラステーブル継承はこんな感じにしてみました。
ここはあまりおもしろい事ないかなぁという感じです。
RoomではEntity間の関連付けができないので他のORMよりは少しばかり実装者に負担がかかりそうですね。
あと、Query結果のcolumn名をfiledと適合するようにrenameの必要があるのが面倒かなぁ。。。 :cry: 

# 具象テーブル継承
```kotlin
@Entity(primaryKeys = ["id"])
class DataAEntity(
        @Embedded
        val data: DataA
)

@Entity(primaryKeys = ["id"])
class DataBEntity(
        @Embedded
        val data: DataB
)

@Dao
interface DataDao {
    @Insert
    fun insert(vararg dataEntity: DataAEntity)

    @Insert
    fun insert(vararg dataEntity: DataBEntity)

    @Query("SELECT id, name, a FROM dataAEntity WHERE name = :name")
    fun findDataAByName(name: String): List<DataA>

    @Query("SELECT id, name, b FROM dataBEntity WHERE name = :name")
    fun findDataBByName(name: String): List<DataB>
}
```

具象テーブル継承はこんな感じにしてみました。
RoomではEmbeddedアノテーションを使うとクラスのfieldをそのままEntityのfiledとして扱うことができます。
すなわちテーブルとサブクラスを直接mappingする具象テーブル継承ではEntityをここまで単純に記述可能になります。
すごい！！

## Embeddedを使う際の注意

実はこのEmbeddedには落とし穴があってModelを別moduleに置いてしまうとbuildが通らないという現象が発生します。。。 :sob: :sob: :sob: 
https://qiita.com/atsuya046/items/758951313fd55f749900

これ結構困るのでなんとかならないものかなぁ :thinking: 

# まとめ

- 単一テーブル継承はNotNull-Nullableのミスマッチを意識しなくなる
- クラステーブル継承は他のORMより実装がちょっと面倒かも
- 具象テーブル継承は（制限はあるが）結構いい感じになる

Roomは他のORMと設計思想が違うので特徴的な部分が多くて面白いですね。
引き続きRoomの特徴見つけていこうと思います。

# 参考
[Room Persistence Library | Android Developers](https://developer.android.com/topic/libraries/architecture/room.html)
[エンタープライズ アプリケーションアーキテクチャパターン (Object Oriented SELECTION)](http://amzn.asia/dInraRa)