3流プログラマのメモ書き

元開発職→社内SE→派遣で営業支援の三流プログラマのIT技術メモ書き。 このメモが忘れっぽい自分とググってきた技術者の役に立ってくれれば幸いです。(jehupc.exblog.jpから移転中)

【Android(Java)】Roomでエンティティアノテーションに@NonNullをつけていないのにビルドするとnotnullになる現象にハマった

問題現象

Android開発(Java)でDBアクセスにRoomライブラリを使ってます。 (roomバージョンは2.5.0です)

各Entityクラスのアノテーションで、DBスキーマがNotNullなら、@NonNullアノテーションをつける必要があります。
逆に、null許容なら、アノテーションをつける必要はないです。

今回操作したいテーブルのスキーマは以下です。

CREATE TABLE [Tbl]( [ID] INTEGER NOT NULL ,[clm1] TEXT ,[clm2] INTEGER , PRIMARY KEY ([ID]) )

上記スキーマに対応して以下のようなEntityクラスを作りました。

@Entity(tableName = "Tbl")
public class Tbl {

    @PrimaryKey
    @NonNull
    @ColumnInfo(name = "ID")
    public int ID;

    @ColumnInfo(name = "clm1")
    public String clm1;

    @ColumnInfo(name = "clm2")
    public int clm2;
}

これで実行すると、DBアクセスで以下ような例外が発生します。 Expected が期待するスキーマ、Foundが実際のスキーマです。

java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Pre-packaged database has an invalid schema: Tbl(com.xxx.app.yyy.Tbl).
 Expected:
TableInfo{name='Tbl', columns={
  ID=Column{name='ID', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='undefined'}, 
  clm1=Column{name='clm1', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}, 
  clm2=Column{name='clm2', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='undefined'}
}, foreignKeys=[], indices=[]}
 Found:                 o
TableInfo{name='Tbl', columns={
  ID=Column{name='ID', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='undefined'}, 
  clm1=Column{name='clm1', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}, 
  clm2=Column{name='clm2', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='undefined'}
}, foreignKeys=[], indices=[]}

clm2が、Entityでは@NonNullアノテーションをつけていないのに、期待するスキーマではnotnullになってしまっています。

これにハマってしまいました。

調査

調査のため、Roomのスキーマを出力することにします。

DataBaseクラスのアノテーションの exportSchema を true にします。

@Database(entities = {Tbl1.class}, version = 2, exportSchema = true)
public abstract class AppDatabase  extends RoomDatabase {
  //DAO定義省略
}

app側の build.gradle に以下設定を追加します。

android {
    defaultConfig {
        //いろいろ
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
            }
        }
    }
}

これで、ビルドすると app/schemas/パッケージ名.AppDatabase/2.jsonスキーマが出力されます。 スキーマを見ると、やはり notNull になっていました。

        "fields": [
          {
            "fieldPath": "ID",
            "columnName": "ID",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "clm1",
            "columnName": clm1",
            "affinity": "TEXT",
            "notNull": false
          },
          {
            "fieldPath": "clm2",
            "columnName": clm2",
            "affinity": "INTEGER",
            "notNull": true
          },

よく見ると INTEGER だけ notnull となっています。

調べると、null許容にするにはエンティティのフィールドの型がnull許容型でないといけないということがわかりました。 intはnull許容型でないので、Room自動的にnotnullにするようです。

対策

ということで対策としては、エンティティのフィールドをnullが使える型にすることで解決しました。
今回は int → Integer となります。使用するときはnullチェックを忘れないようにしないといけないですが。

@Entity(tableName = "Tbl")
public class Tbl {

    @PrimaryKey
    @NonNull
    @ColumnInfo(name = "ID")
    public int ID;

    @ColumnInfo(name = "clm1")
    public String clm1;

    @ColumnInfo(name = "clm2")
    public Integer clm2;
}

これに気付くのに結構時間かかったので、ビルド時に警告とかで出してほしいなーとおもいました。