Android RoomのautoGenerateの挙動

以下の定義のテーブルに対してINSERTをすることでデータがどのように更新されていくかを確認する。

@Entity(tableName = "Sample")
data class SampleEntity(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val name: String
)

環境は以下の通り

利用しているコード(検証用アプリ)はこちら

github.com

autoGenerate=trueとは

公式の説明から以下のことがわかる PrimaryKey  |  Android Developers

  • SQLiteが一意のIDを生成する
  • データの型はINTEGERになる
  • INSERT時に0またはNULLは「値がセットされてない」とみなされる

Shema確認

RoomDatabaseexportSchema = true設定し、コンパイルオプションでroom.schemaLocationを指定した状態でビルドをすると、スキーマJSON形式でファイル出力される。上記SampleテーブルのCREATE文は以下のようになっている。

CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (
    `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
    `name` TEXT NOT NULL
)

idカラムがAUTOINCREMENT指定されている。

AUTOINCREMENT

公式を読む。

sqlite.org

  • INTEGER PRIMARY KEYで修飾されるカラムはROWIDエイリアスである
  • ROWIDまたはINTEGER PRIMARY KEYに対する値が明示的にセットされていない場合にはSQLiteが未使用の数値を自動で割り当てる
    • この数値は通常利用されている数値の最大値 + 1 になる
    • この挙動はAUTOINCREMENTがあってもなくても同じである
  • AUTOINCREMENTが指定されていると「DBのライフサイクルを通してIDの再利用をしない」というようなアルゴリズムになる
    • つまり「過去に削除されたレコードがもっていたIDの使用を避ける」ということである

Summaryを読めばだいたいのことがわかる(この記事ではROWIDの説明は省略)。

INSERT

次のようなDaoメソッドを用意して、SampleEntityidを未設定(デフォルト値である0)でINSERTを行ってみる。

@Insert
suspend fun insertIncrementId(sampleEntity: SampleEntity)

すると、idが1, 2,3.....と順に振られてレコードがinsertされていくのがわかる。期待通りの動作だ。

複数のデータをinsertする場合はどうだろうか。

@Insert
suspend fun updateIncrementId(sampleEntity: List<SampleEntity>)

レコードを3つずつinsertしてもidは順にincrementされていくのがわかる

また一度全レコードをDELETEした後にinsertすれば、「DBのライフサイクルを通してIDの再利用をしない」ことも確認できる。

@Query("DELETE FROM Sample")
suspend fun deleteAll()

なおidを明示的に指定してINSERTした場合の挙動はOnConflictStrategyに従うがここでは深入りしない。

クエリの確認

Roomアノテーションプロセッサでビルド時にコードを生成する。@InsertアノテーションからはINSERTクエリが生成される。

INSERT OR ABORT INTO `Sample` (`id`,`name`) VALUES (nullif(?, 0),?);

OR ABORTの部分はOnConflictStrategyによって変わるが、だいたいこのようなクエリになる。生成されるのはJavaのコードなので、nullIf(?, 0)の部分はInteger(正確にはKotlinのInt)がnullの場合を考慮していると思われる。

所感

  • 主キーになるような明示的なカラムがなく、一意の整数値IDを付与したい場合には有効
    • また何らかの理由でinsert順を保持したい場合にも有効かもしれない
  • 一方で、INTEGER PRIMARY KEY AUTOINCREMENTなカラムがあると他にPrimaryKeyを追加することはできないため、明に主キーになるカラムがあるのであればそちらを使うべきだと思われる
    • 逆言えば、このときAUTOINCREMENTは使えなくなるので、必要があれば何らかの手段で代替する必要があるだろう