既存のSQLiteのデータベースをArchitecture CompontentsのRoomに移行してみた

#概要

今までアプリをSQLiteで作ってきたが、今更ながらに重い腰を上げ、Roomベースに移行しました。

なお、ここでの移行は前データを引き継いで移行するという意味で、ユーザーのデータは削除せずに進めたいと思います。
あと、この際なのでKotlinにしようぜと意気込んだのでKotlinのコードとなります。

#基本

基本はドキュメントです。
https://developer.android.com/training/data-storage/room/index.html

これ通りで作れば大体問題ないです。以下少し変えないといけなかった所。

#ハマりどこ

##1. データベースのファイル名

当たり前といえば当たり前なのですが、通常実装時とRoomでの実装時で名前を変えない。

元々自分のコードは`SQLiteOpenHelper`を使ってたのですが例えば、

```java
SQLiteOpenHelper helper = new SQLiteOpenHelper(context, "app.db", null, 1);
```

こんなコードだった場合に、`AppDatabase`を取得する際の名前は

```kotlin
Room.databaseBuilder(context, AppDatabase::class.java, "app.db").build()
```

となります。同時の自分がなぜそう考えたかわからないですが、.dbって勝手につくもんだと解釈し、`"app"`って入れてました。

##2. 移行方法

最終的にやらなきゃいけなかったことは

1. RoomでdatabaseBuilderを使ってDBにアクセスする際にMigrationを行わないといけない
2. テーブルの内容とEntityを合わせないといけない

この2点のみ。簡単ですが1個ずつ説明します。

### 2-1. RoomでdatabaseBuilderを使ってDBにアクセスする際にMigrationを行わないといけない
まず、1についてですが、単純に何もしないMigrationを行う必要があります。どうも、Roomの内部でマスターのデータを持っているらしく、Migrationを行わないと以下のようなエラー文言がでます。

>Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.

ということで以下のような形で、何もしないMigrationを行います。

```kotlin
Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
    .addMigrations(object: Migration(1, 2){
        override fun migrate(database: SupportSQLiteDatabase) {
        }
    })
    .build();
```

###2-2. テーブルの内容とEntityを合わせないといけない
そして2について。

上記のようなMigrationを書いて起動すると、恐らく以下のようなエラーが出ると思います。

>Caused by: java.lang.IllegalStateException: Migration didn't properly handle

これはようは、既にあるDBファイルのバージョンをあげようとして、Migrationに失敗したということですね。上記で指定したように、Migraitonの内容は空なのになぜ失敗したか？その理由はその続きに書いてあります。

>Expected:
　TableInfo{...}
Found:
　TableInfo{...}

このTableInfoの中身が重要です。ここにはDBのSchemaが書いてあり、Expected(今やろうとしているMigration後のSchema)とFound(現状のSchema)が合わないとMigrationがうまくできません。
>※補足：
なお、Shchemaをあわせないといけないのは今回の場合、Migrationの内容が空だからです。逆にMigrationでちゃんと書いてあげるのも1つの手ですが、元のDBを引き継いで使うために行っているので、Migrationでは対応せずSchemaを合わせることで対応します。

合わせるにあたって、上記のExpectedとFoundの中身を一個一個見ていけばいいのですが、ハマりどころはこんなところでした。

####◯テーブル名を合わせる
当たり前ですがテーブル名が合わないと駄目です。Entityのクラス名がテーブル名となります。テーブル名がPascalCaseじゃない場合には注意が必要です。Entityのアノテーションにテーブル名が指定できます。

```kotlin
@Entity(tableName = "user")
class User{
}
```

####◯カラム名を合わせる
こちらも当たり前ですがカラム名が合わないと駄目です。Roomでは、Entityのクラス内にある変数名がカラム名となります。よって、変数名とカラム名が違う場合には以下のようにアノテーションで指定して上げる必要があります。

```kotlin
@ColumnInfo(name = "first_name")
val firstName: String? = null
```

####◯notNullを合わせる

テーブルを作成する時に指定しなければ、基本はnotNull=trueになっていると思います。ここで紛らわしいのがnotNullがtrue、つまり、notNullを許容するとなります。よってCreate TableのSQL文で言うと、こんな感じです。

```sql
"CREATE TABLE user (
  id INTEGER PRIMARY KEY NOT NULL /* notNull=false */,
  first_name TEXT /* notNull=true */
);
```

そして、notNullをtrueにする方法ですが、StackOverflowなんかを見ていると、notNull=trueの変数名に対して、＠NonNullをつけろと言うのが出てきます。

```java
@NonNull
private int id;
```

今あえて、Javaで書いたのですが、これは**javaのときのみ有効です**。

Kotlinではそもそも言語側でNull Safeにかけるため、そこを指標に作ります(というか多分ですがKotlinから生成されるコードに＠NonNullがつくんじゃないかと思う)。よってKotlinの場合は、こんな感じ。

```kotlin
var id: Int = 0 //notNull=false
var firstName: Int? = 0 //notNull=true
```

自分のテーブルでは無かったですが、他にはUniqueとかも別途で設定が必要なようです。

#まとめ
以上で無事移行できるはずです。意外とまとまった情報が無かったので、お役に立てると嬉しいです。