9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Android]RoomでInsert時にOnConflictStrategy.REPLACEを使う際の注意点

Posted at

Android のプロジェクトで Room を使用してハマったことがあったので共有したいと思います。

結論から言うと、ForeignKey で指定される Entity があった場合、その Entity を Insert する際に OnConflictStrategy.REPLACE を使用してはいけません。

理由

Dao で @Insert アノテーションに OnConflictStrategy.REPLACE を指定すると、SQL 的には INSERT OR REPLACE 文が実行されます。

SQLite の仕様では INSERT OR REPLACE 文では既存の行を削除した上で、行を追加、更新するようになっています。

参考: https://sqlite.org/lang_conflict.html

When a UNIQUE or PRIMARY KEY constraint violation occurs, the REPLACE algorithm deletes pre-existing rows that are causing the constraint violation prior to inserting or updating the current row

ですので REPLACE の対象が ForeignKey で指定されている Entity のレコードであった場合に意図しない動作が発生する場合があります。

記事お気に入りの記事 の Entity が以下のように定義されていたとします。

お気に入りの記事 では ForeignKey記事 の Entity が指定されています)

Article.kt
// 記事
@Entity(tableName = "articles")
data class Article(
    @PrimaryKey val id: Int,
    val title: String,
    val contents: String
)
FavoriteArticle.kt
// お気に入りの記事
@Entity(
    tableName = "favorite_articles",
    foreignKeys = [
        ForeignKey(
            entity = Article::class,
            parentColumns = ["id"],
            childColumns = ["articleId"],
            onDelete = ForeignKey.CASCADE
        )
    ]
)
data class FavoriteArticle(
    @PrimaryKey val articleId: Int
)

そして Dao で以下のようなメソッドを定義していたとします。

ArticleDao.kt
@Dao
abstract class ArticleDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    abstract fun insertArticle(article: Article)
    
    ...
}

そして 記事お気に入りの記事 のレコードがそれぞれデータベースに保存されていた場合に、上記の Dao のメソッドで既存の 記事 を更新するとデータベースに保存されている関連した お気に入りの記事 が削除されてしまいます。

ForeignKeyonDelete に設定している値によって お気に入りの記事 がどうなるか挙動は変わりますが、この例の場合だと ForeignKey.CASCADE を設定しているので削除されます)

解決方法

例えば以下の例のように OnConflictStrategy.IGNORE などに設定しておき、Transaction を張ってレコードがあれば更新、なければ新規作成のようにする方法が考えられます。

ArticleDao.kt
@Dao
abstract class ArticleDao {

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    abstract fun insertArticle(article: Article)

    @Query("UPDATE articles SET title = :title, contents = :contents WHERE id = :articleId")
    abstract fun updateArticle(articleId: Int, title: String, contents: String)

    @Query("SELECT COUNT(*) FROM articles WHERE id = :articleId")
    abstract fun countArticle(articleId: Int): Int

    @Transaction
    open fun insertOrUpdateArticle(article: Article) {
        if (countArticle(article.id) > 0) {
            // レコードがあれば更新
            updateArticle(article.id, article.title, article.contents)
        } else {
            // レコードがなければ新規作成
            insertArticle(article)
        }
    }
    
    ...
}
9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?