kotlin 如何添加到以前预填充的房间数据库?

m1m5dgzv  于 2023-01-02  发布在  Kotlin
关注(0)|答案(1)|浏览(182)

之前,我预先填充了一些数据并将其添加到Room数据库中。但是我需要添加一些新数据。作为添加这些数据的一种方法,我可以通过直接请求dao来完成,但是应用程序每次打开时都会这样做。这将给应用程序带来不必要的负担。因此,是否有比下面的代码更好的方法?我想知道是否有比这更好的方法。

private fun addNewWord(){
    val newWord1 = Word(1, "John", "Weight")
    val newWord2 = Word(2, "Alex", "So"
    wordViewModel.addWord(newWord1, newWord2)
}

我找到了一个类似上面代码的解决方案,但我认为它不够正确。我正在寻找一个更好的方法。这是一个最佳实践吗?

3pmvbmvn

3pmvbmvn1#

有许多更好的(只要 * 这将给应用程序带来不必要的负担 *),假设不必要的负担是与每当应用程序运行时尝试插入记录相关的开销(每当数据库打开时更正确)。

最简单的解决方案,但不是真正更好的解决方案(甚至可能是您尝试过的)是忽略重复项(如果您还没有忽略它们的话),这涉及到使用INSERT OR IGNORE ....,其中一个或多个列或列的组合具有UNQIUE索引。

所有房间表(FTS(全文搜索)除外)都必须具有主键主键隐式为UNIQUE。因此,如果使用INSERT OR IGNORE ....,则将忽略UNIQUE CONFLICT(不插入行,并忽略会导致异常的冲突)。
要指定INSERT OR IGNORE以方便@Insert,则可以指定@Insert注解的onConflict值。例如

@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(word: Word): Long
  • 便利性@Insert返回插入行的rowid,或者如果由于忽略该行而没有插入该行,则返回-1,这可以被检查以查看是否插入了行。
  • 存在于“所有房间”表格 * 中的通常隐藏的列(如同存在于任何不是虚拟表格或定义为WITHOUT ROWID表格的SQLite表格中一样)*。

您还可以通过@Entity注解的indicies参数为列或列组合指定唯一索引,例如:-

@Entity(
    indices = [
        /* A composite UNIQUE index on word1 combined with word2 */
        Index(value = ["word1","word2"], unique = true)
        /* example
        * if a row exists that has A as word1 and B as word2 then (index value can be considered as AB):-
        *       inserting a new row with B and A would be inserted (index value BA)
        *       inserting a new row with B and B (or A and A) would be inserted (index value BB or AA)
        *       inserting a row with B and A would result in a UNIQUE CONFLICT
        *       inserting a row with BA and nothing (aka null) would be inserted (assuming NOT NULL was not coded or implied for column word2)
        */

        /*
            An Index on a single column (word1 so the value in the word1 column cannot be duplicated)
            NOTE the above would not apply if this index was also included
            In this case then word1 =A and word2 = A above (third insert (or A and A)) would be considered a duplicate
         */
        , Index(value = ["word1"], unique = true)
])
data class Word(
    @PrimaryKey
    var id: Long?=null,
    var word1: String,
    var word2: String
)

但是,这个简单的解决方案仍然会运行,并在应用程序运行时尝试插入新数据。
没有 “不必要负担”的更好解决方案

如果目标是只应用一次新数据,则需要有一种方法来查看数据是否已经应用,可能是通过迁移(即新版本)。
迁移将仅运行一次,因为用户版本是数据库文件头的一部分,由Room检查/更新。
如果在指定新数据库版本后安装应用程序,也将运行迁移。

工作演示

也许可以考虑以下基于迁移的工作演示,基于您的数据似乎是:-
房间数据库代码:-

@Entity(
    indices = [
        /* A composite UNIQUE index on word1 combined with word2 */
        Index(value = ["word1","word2"], unique = true)
        /* example
        * if a row exists that has A as word1 and B as word2 then (index value can be considered as AB):-
        *       inserting a new row with B and A would be inserted (index value BA)
        *       inserting a new row with B and B (or A and A) would be inserted (index value BB or AA)
        *       inserting a row with B and A would result in a UNIQUE CONFLICT
        *       inserting a row with BA and nothing (aka null) would be inserted (assuming NOT NULL was not coded or implied for column word2)
        */
])
data class Word(
    @PrimaryKey
    var id: Long?=null,
    var word1: String,
    var word2: String
)

@Dao
interface TheDAOs {
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(word: Word): Long
    @Query("SELECT * FROM word")
    fun getAllWords(): List<Word>
}

const val DATABASE_NAME = "the_database.db"
const val ASSET_NAME = DATABASE_NAME
@Database(entities = [Word::class], exportSchema = false, version = 1)
abstract class TheDatabase: RoomDatabase() {
    abstract fun getTheDAOs(): TheDAOs

    companion object {
        private var instance: TheDatabase?=null
        fun getInstance(context: Context): TheDatabase {
            if (instance==null) {
                instance = Room.databaseBuilder(context,TheDatabase::class.java, DATABASE_NAME)
                    .createFromAsset(ASSET_NAME,pdc)
                    .allowMainThreadQueries() /* For convenience of the demo */
                    .addCallback(cb)
                    .addMigrations(mig1to2)
                    .build()
            }
            return instance as TheDatabase
        }
        private val mig1to2 = object: Migration(1,2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                Log.d("MIG1-2", "Migration is running")
                val cv = ContentValues()
                cv.put("word1", "NEWWORD W1=W")
                cv.put("word2", "NEWWORD W2=W")
                database.insert("word", OnConflictStrategy.IGNORE, cv)
                cv.clear()
                cv.put("word1", "NEWWORD W1=X")
                cv.put("word2", "NEWWORD W2=X")
                database.insert("word", OnConflictStrategy.IGNORE, cv)
                cv.clear()
                cv.put("word1", "NEWWORD W1=Y")
                cv.put("word2", "NEWWORD W2=Y")
                database.insert("word", OnConflictStrategy.IGNORE, cv)
                cv.clear()
                cv.put("word1", "NEWWORD W1=Z")
                cv.put("word2", "NEWWORD W2=Z")
                database.insert("word", OnConflictStrategy.IGNORE, cv)
            }
        }
        val cb = object: RoomDatabase.Callback() {
            val TAG = "DBCALLBACK"

            override fun onCreate(db: SupportSQLiteDatabase) {
                super.onCreate(db)
                Log.d(TAG,"onCreate called")
            }

            override fun onOpen(db: SupportSQLiteDatabase) {
                super.onOpen(db)
                Log.d(TAG,"onOpen called")
            }

            override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
                super.onDestructiveMigration(db)
                Log.d(TAG,"onDestructiveMigration called")
            }

        }
        val pdc = object: PrepackagedDatabaseCallback(){
            val TAG = "PPDOPEN"
            override fun onOpenPrepackagedDatabase(db: SupportSQLiteDatabase) {
                super.onOpenPrepackagedDatabase(db)
                Log.d(TAG,"Prepackaged Database has been copied and opened")
            }
        }
    }
}
  • 注意数据库版本是1
  • 包括回调以显示何时调用以及调用什么。
  • 文件the_database.db位于资产文件夹中,并具有3行,如下所示:-

使用的活动代码为:-

class MainActivity : AppCompatActivity() {

    lateinit var db: TheDatabase
    lateinit var dao: TheDAOs
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        db = TheDatabase.getInstance(this)
        dao = db.getTheDAOs()
        for(w in dao.getAllWords()) {
            Log.d("DBINFO","Word ID is ${w.id} WORD1 is ${w.word1} WORD2 is ${w.word2}")
        }
    }
}

例如,它只是访问数据库,提取所有行并将数据写入日志。
当在数据库版本1上作为新安装运行时,日志的输出为:-

2023-01-02 15:26:47.055 D/PPDOPEN: Prepackaged Database has been copied and opened
2023-01-02 15:26:47.119 D/DBCALLBACK: onOpen called
2023-01-02 15:26:47.124 D/DBINFO: Word ID is 1 WORD1 is ORIGINALWORD1=W1_A WORD2 is  ORIGINALWORD2=W2_A
2023-01-02 15:26:47.124 D/DBINFO: Word ID is 2 WORD1 is ORIGINALWORD1=W1_B WORD2 is  ORIGINALWORD2=W2_B
2023-01-02 15:26:47.124 D/DBINFO: Word ID is 3 WORD1 is ORIGINALWORD1=W1_C WORD2 is  ORIGINALWORD2=W2_C

如果再次运行,仍然是版本1,则输出为:-

2023-01-02 15:28:27.976 D/DBCALLBACK: onOpen called
2023-01-02 15:28:27.981 D/DBINFO: Word ID is 1 WORD1 is ORIGINALWORD1=W1_A WORD2 is  ORIGINALWORD2=W2_A
2023-01-02 15:28:27.981 D/DBINFO: Word ID is 2 WORD1 is ORIGINALWORD1=W1_B WORD2 is  ORIGINALWORD2=W2_B
2023-01-02 15:28:27.981 D/DBINFO: Word ID is 3 WORD1 is ORIGINALWORD1=W1_C WORD2 is  ORIGINALWORD2=W2_C

也就是说,预打包数据库的副本没有被调用,因为数据库已经存在,否则输出是相同的。
如果数据库版本更改为2(无模式更改)且重新运行应用程序,则输出为:-

2023-01-02 15:31:32.464 D/MIG1-2: Migration is running
2023-01-02 15:31:32.529 D/DBCALLBACK: onOpen called
2023-01-02 15:31:32.536 D/DBINFO: Word ID is 1 WORD1 is ORIGINALWORD1=W1_A WORD2 is  ORIGINALWORD2=W2_A
2023-01-02 15:31:32.536 D/DBINFO: Word ID is 2 WORD1 is ORIGINALWORD1=W1_B WORD2 is  ORIGINALWORD2=W2_B
2023-01-02 15:31:32.536 D/DBINFO: Word ID is 3 WORD1 is ORIGINALWORD1=W1_C WORD2 is  ORIGINALWORD2=W2_C
2023-01-02 15:31:32.536 D/DBINFO: Word ID is 4 WORD1 is NEWWORD W1=W WORD2 is NEWWORD W2=W
2023-01-02 15:31:32.536 D/DBINFO: Word ID is 5 WORD1 is NEWWORD W1=X WORD2 is NEWWORD W2=X
2023-01-02 15:31:32.536 D/DBINFO: Word ID is 6 WORD1 is NEWWORD W1=Y WORD2 is NEWWORD W2=Y
2023-01-02 15:31:32.536 D/DBINFO: Word ID is 7 WORD1 is NEWWORD W1=Z WORD2 is NEWWORD W2=Z

即调用迁移,并按照迁移中的代码引入新数据。
如果重新运行应用程序(仍为版本2),则:-

2023-01-02 15:34:21.336 D/DBCALLBACK: onOpen called
2023-01-02 15:34:21.342 D/DBINFO: Word ID is 1 WORD1 is ORIGINALWORD1=W1_A WORD2 is  ORIGINALWORD2=W2_A
2023-01-02 15:34:21.342 D/DBINFO: Word ID is 2 WORD1 is ORIGINALWORD1=W1_B WORD2 is  ORIGINALWORD2=W2_B
2023-01-02 15:34:21.342 D/DBINFO: Word ID is 3 WORD1 is ORIGINALWORD1=W1_C WORD2 is  ORIGINALWORD2=W2_C
2023-01-02 15:34:21.342 D/DBINFO: Word ID is 4 WORD1 is NEWWORD W1=W WORD2 is NEWWORD W2=W
2023-01-02 15:34:21.342 D/DBINFO: Word ID is 5 WORD1 is NEWWORD W1=X WORD2 is NEWWORD W2=X
2023-01-02 15:34:21.342 D/DBINFO: Word ID is 6 WORD1 is NEWWORD W1=Y WORD2 is NEWWORD W2=Y
2023-01-02 15:34:21.342 D/DBINFO: Word ID is 7 WORD1 is NEWWORD W1=Z WORD2 is NEWWORD W2=Z

即不调用迁移,所有数据保留。
如果应用程序被卸载,然后安装/运行(根据新安装),则:-

2023-01-02 15:37:25.096 D/PPDOPEN: Prepackaged Database has been copied and opened
2023-01-02 15:37:25.113 D/MIG1-2: Migration is running
2023-01-02 15:37:25.169 D/DBCALLBACK: onOpen called
2023-01-02 15:37:25.175 D/DBINFO: Word ID is 1 WORD1 is ORIGINALWORD1=W1_A WORD2 is  ORIGINALWORD2=W2_A
2023-01-02 15:37:25.175 D/DBINFO: Word ID is 2 WORD1 is ORIGINALWORD1=W1_B WORD2 is  ORIGINALWORD2=W2_B
2023-01-02 15:37:25.175 D/DBINFO: Word ID is 3 WORD1 is ORIGINALWORD1=W1_C WORD2 is  ORIGINALWORD2=W2_C
2023-01-02 15:37:25.175 D/DBINFO: Word ID is 4 WORD1 is NEWWORD W1=W WORD2 is NEWWORD W2=W
2023-01-02 15:37:25.175 D/DBINFO: Word ID is 5 WORD1 is NEWWORD W1=X WORD2 is NEWWORD W2=X
2023-01-02 15:37:25.175 D/DBINFO: Word ID is 6 WORD1 is NEWWORD W1=Y WORD2 is NEWWORD W2=Y
2023-01-02 15:37:25.176 D/DBINFO: Word ID is 7 WORD1 is NEWWORD W1=Z WORD2 is NEWWORD W2=Z

即,已复制预打包数据库并调用迁移,从而引入新数据(不在预打包数据库中)。

相关问题