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;
}

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

さくらのVPSで独自ドメイン利用中に、さくらのメールボックスを追加する場合のドメイン設定の罠

要件

現状:  さくらのVPSでWEBサーバーと、メールサーバーを構築しサービス提供中。  さくらでドメイン取得し、さくらのDNSサーバーにてゾーン情報を管理。 要件:  メールサーバーのみ、さくらのレンタルサーバー(メールボックスプラン)を契約しそちらで運用するようにしたい。

注意点 - 設定にゾーン削除が必要

ドメインが新規(ゾーン情報が未登録)であれば、レンタルサーバーのコントロールパネルからドメイン設定するだけでいいみたいなのですが、既にゾーン情報が登録されていると厄介です。
ゾーン情報が登録された状態で、レンタルサーバーのコントロールパネルからドメイン追加しようとすると、エラーになります。
サポートに問い合わせたところ、以下の回答でした。

該当ドメインは、すでにさくらのネームサーバ上にゾーンが生成されており、この状態のままメールボックスへとドメインを追加登録するとエラーとなる。
そのため、一旦現在設定されているレコード情報をすべてメモし、該当ドメインのゾーンを削除後、2時間ほど間隔をあけ、メールボックスへと追加が必要。
ゾーン削除後は、必ず2時間ほど間隔をあけることが必要。 ※さくらのネームサーバ上のゾーン初期化に、2時間ほど要する場合があるため。

ゾーン情報を削除しないといけないというのは、罠でした。。。

既にゾーン情報がある場合、いったんゾーン情報を削除し、レンタルサーバーのコントロールパネルでドメイン設定 → (2時間待ち)ゾーン情報を再設定 となるようです。
ゾーン情報を削除するので、2時間+アルファのダウンタイムが発生するわけですね。
メールだけでなく該当ドメインを使ったサービスが2時間以上ダウンするということになります。

なお、追加するドメインサブドメインであれば、ゾーン情報を削除せずとも、ドメインコントロールパネルからサブドメインのレコードだけ削除すれば登録できました。
''' 例: <ゾーン情報> エントリ名 タイプ データ subdomain A xxx.xxx.xxx.xxx. MX subdomain.hogehoge.example  上記 subdomain のレコードを削除。

さくらレンタルサーバー コントロールパネル→ドメイン/SSLドメイン新規追加 → hogehoge.example を選択し、[サブドメインを指定する]にチェックし、サブドメインを入力し、[追加]。 '''

手順

  1. ドメインコントロールパネルにて、ゾーン情報をスクショなり、コピペなりで保存。
  2. [ゾーン削除]でゾーン情報を削除。
  3. 2時間待つ。
  4. レンタルサーバー側でドメイン追加。
  5. 4.の結果、以下ゾーン情報が自動で作成されるのでドメインコントロールパネルにて確認。
エントリ名 タイプ  データ
@          NS      ns1.dns.ne.jp.
           NS      ns2.dns.ne.jp.
           A       レンタルサーバのIP
           MX      10 @ -
           TXT     "v=spf1 a:xxxx.sakura.ne.jp mx ~all"
www        CNAME   @
mail       CNAME   @
ftp        CNAME   @

6.1.で保存したゾーン情報を設定。MXレコードは、初期ドメインの値にする。
 なお、MXレコードの値に"."を入れるのを忘れると、"レコードの参照先にAレコード以外が指定されています" というエラーになります。
 複数レコードだと、どのレコードがおかしいのかわかりづらく、この辺はわかりやすいようにしてほしいですね。。

注意点 - メーラーの送受信メールサーバーで独自ドメインは利用できなくなる

もう1点、罠がありました。
ヘルプには、暗号化設定を利用しない場合、メーラーのメールサーバー設定値に独自ドメインが可能とあります。(メールソフトによっては警告が出るとの注釈有。)

しかし、独自ドメインメーラーに設定すると、メールサーバーに接続できません。

サポートに問い合わせたところ、以下の回答でした。

現在、ウェブをVPSに、メールをさくらのメールボックスの初期ドメインに設定しているが、
MXレコードを初期ドメインで設定するとメールソフトの送信/受信サーバーも初期ドメインで設定する必要がある。
ウェブをメールと別のサーバでご利用いただく場合、MXレコードは初期ドメインで設定する必要があるので、独自ドメインで設定できない。
独自ドメインで設定できる可能性がある方法としては、さくらのレンタルサーバシリーズに乗り換えて、
Aレコードもレンタルサーバで利用するなら、MXレコードを独自ドメインで設定できる。 

考えてみればまぁ当たり前でしたね。
MXレコードの値はあくまでメール配信時に使われるもので、メーラーに設定したドメイン名は、Aレコードで解決されます。
メールサーバーとWEBサーバーが同一IPなら、独自ドメインでもOKでしょうが、今回のように別IPになると、メールサーバー側のIPに紐づいたドメイン名にしないといけません。

ただ、ヘルプに書いてあったのでそのままうのみしちゃってたので、WEBとメールサーバーが違う場合は、初期ドメインになるなどの注記は欲しかったところです。