facebook twitter hatena line email

「Android/kotlin/db/room」の版間の差分

提供: 初心者エンジニアの簡易メモ
移動: 案内検索
(insert失敗時の処理)
(db定義変更時)
行264: 行264:
 
以下エラーが発生する場合
 
以下エラーが発生する場合
 
<pre>
 
<pre>
Caused by: java.lang.IllegalStateException:  
+
Caused by: java.lang.IllegalStateException: 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.
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.
+
 
</pre>
 
</pre>
 
app.gradle
 
app.gradle

2020年3月16日 (月) 17:33時点における版

Roomとは

SQLiteのマッパーライブラリ

公式:https://developer.android.com/training/data-storage/room

Androidでも公式が、強くおすすめすると書いてる。

準備

app/build.gradle

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
dependencies {
    implementation 'android.arch.persistence.room:runtime:1.0.0'
    annotationProcessor  'android.arch.persistence.room:compiler:1.0.0'
    kapt 'android.arch.persistence.room:compiler:1.0.0'
    
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3"
}

サンプル

schema1.usersのテーブルを置く時のサンプル

Schema1Database.kt

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase

@Database(entities = arrayOf(UserEntity::class), version = 1)
abstract class Schema1Database: RoomDatabase() {
    abstract fun userDao(): UserDao
    companion object {
        @Volatile
        private var INSTANCE: Schema1Database? = null
        fun getDatabase(context: Context): Schema1Database {
            val tempInstance = INSTANCE
            if (tempInstance != null) {
                return tempInstance
            }
            synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    Schema1Database::class.java,
                    "schema1.db"
                ).build()
                INSTANCE = instance
                return instance
            }
        }
    }
}

UserEntity.kt

import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "users")
data class UserEntity constructor(
    @PrimaryKey(autoGenerate = true)
    val id: Int,
    val name: String
)

UserDao.kt

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import androidx.room.Delete
import androidx.room.OnConflictStrategy

@Dao
interface UserDao {
    @Query("SELECT * from users")
    fun getAll(): List<UserEntity>

    @Query("SELECT * FROM users WHERE id IN (:userIds)")
    fun loadAllByIds(vararg userIds: Int): List<UserEntity>

    @Query("SELECT * FROM users WHERE name LIKE :name")
    fun findByLikeName(name: String): UserEntity

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(user: UserEntity)

    @Insert
    fun insertAll(vararg users: UserEntity)

    @Update
    fun update(user: UserEntity)

    @Update
    fun updateUsers(vararg users: UserEntity)

    @Delete
    fun delete(user: UserEntity)

    @Delete
    fun deleteUsers(users: List<UserEntity>)

    @Query("DELETE FROM users")
    fun deleteAll()
}

@Insert(onConflict = OnConflictStrategy.REPLACE) は同一idの場合に置き換えされる

db呼び出し

MainActivity.kt

class MainActivity : AppCompatActivity() {
    val scope = CoroutineScope(Dispatchers.Default)
    lateinit var db: Schema1Database
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        db = Schema1Database.getDatabase(applicationContext)
        scope.launch {
            dbTask()
        }
    }
    private suspend fun dbTask() {
        try {
            dbExec()
        } catch (e: Exception) {
            Log.e(localClassName, "onCancelled", e)
        }
    }
    private fun dbExec() {
        val dao = db.userDao()
        dao.insert(UserEntity(0, "rei"))
        dao.insert(UserEntity(1, "taro"))
        dao.insert(UserEntity(2, "jiro"))
        var users: List<UserEntity> = dao.getAll()
        for (user: UserEntity in users) {
            Log.i("db", "id=" + user.id.toString() + " name=" + user.name);
        }
        for (user: UserEntity in users) {
            if (user.id == 2) {
                Log.i("db", "upd id=" + user.id.toString() +" name=" + user.name);
                dao.update(UserEntity(user.id, "siro"))
            } else {
                Log.i("db", "del id=" + user.id.toString() +" name=" + user.name);
                dao.delete(UserEntity(user.id, user.name));
            }
        }
        users = dao.getAll()
        for (user: UserEntity in users) {
            Log.i("db", "del id=" + user.id.toString() +" name=" + user.name);
            dao.delete(UserEntity(user.id, user.name));
        }
    }
}

出力

2020-01-14 16:16:51.109 24379-24416/? I/db: id=1 name=taro
2020-01-14 16:16:51.109 24379-24416/? I/db: id=2 name=jiro
2020-01-14 16:16:51.109 24379-24416/? I/db: id=21 name=rei
2020-01-14 16:16:51.109 24379-24416/? I/db: del id=1 name=taro
2020-01-14 16:16:51.110 24379-24416/? I/db: upd id=2 name=jiro
2020-01-14 16:16:51.111 24379-24416/? I/db: del id=21 name=rei
2020-01-14 16:16:51.114 24379-24416/? I/db: del id=2 name=siro

idが0のinsertはauto_incrementのmax値が入る?

更新処理

上記例では新規UserEntityを作って入れたが、

dao.update(UserEntity(user.id, "siro"))

変更するプロパティをvalからvarに変えて、更新していれることもできる

UserEntity.kt

data class UserEntity constructor(
    @PrimaryKey(autoGenerate = true)
    val id: Int,
    var name: String
)
user.name = "siro"
dao.update(user)

以下のような修飾子を用意

@Insert(onConflict = OnConflictStrategy.REPLACE)
@Insert(onConflict = OnConflictStrategy.ROLLBACK)
@Insert(onConflict = OnConflictStrategy.ABORT)

参考:https://tech.recruit-mp.co.jp/mobile/post-12311/

insertしたときにidを返す場合

戻り値の型にLongを設定すれば良い。

@Dao
interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(user: UserEntity): Long
}

呼び出しサンプル

var id = dao.insert(UserEntity(2, "jiro"))
Log.i("db", "id=" + id.toString()) // 2

updateした行数を返す場合

updateの戻り値の型にIntを設定する

@Dao
interface UserDao {
    @Update
    fun update(user: UserEntity): Int
}

呼び出しサンプル

var cnt = dao.update(user)
Log.i("db", "cnt=" + cnt.toString()) // 1

sql失敗時の処理

try {
    var ret = dao.insert(UserEntity(9223372036854775807, "jiro"))
    Log.i("test", "ret insert id=" + ret.toString())
} catch (e: SQLiteException) {
    Log.i("test", "error message=" + e.message)
}

sqliteのintの上限値(9223372036854775807)で一度データを入れ、次にidを0指定してauto_incrementさせると以下エラーが発生した。

cannot rollback - no transaction is active (code 1 SQLITE_ERROR)

insert失敗時の処理

isnertをIGNOREにしてみて、同じidを入れてみた所、失敗として、-1が返ってきた。

UserDao.kt

@Dao
interface UserDao {
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(user: UserEntity): Int
}

MainActivity.kt

 var ret = dao.insert(UserEntity(1234, "jiro"))
 Log.i("test", "ret insert id=" + ret.toString()) // 1234
 var ret2 = dao.insert(UserEntity(1234, "jiro"))
 Log.i("test", "ret2 insert id=" + ret2.toString()) // -1

参考:https://tutorialmore.com/questions-756651.htm

db定義変更時

以下エラーが発生する場合

Caused by: java.lang.IllegalStateException: 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.

app.gradle

参考