(Android) 如何预填充 Room 数据库

(Anrdoid) How to prepopulate the Room database

我制作了一个像当前图像一样的屏幕。

目前正在通过从 strings.xml 资源文件中获取设置 A, B, C.. 等数据。

我现在要使用 Room DB 而不是 strings.xml 我想从 Room.

获取这些数据

为此,我们需要用数据预填充 Room

在我找到的示例代码中,通常使用名为addCallback()的方法。

像这样:

@Database(entities = arrayOf(Data::class), version = 1)
abstract class DataDatabase : RoomDatabase() {

    abstract fun dataDao(): DataDao

    companion object {

        @Volatile private var INSTANCE: DataDatabase? = null

        fun getInstance(context: Context): DataDatabase =
                INSTANCE ?: synchronized(this) {
                    INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
                }

        private fun buildDatabase(context: Context) =
                Room.databaseBuilder(context.applicationContext,
                        DataDatabase::class.java, "Sample.db")
                        // prepopulate the database after onCreate was called
                        .addCallback(object : Callback() {
                            override fun onCreate(db: SupportSQLiteDatabase) {
                                super.onCreate(db)
                                // insert the data on the IO Thread
                                ioThread {
                                    getInstance(context).dataDao().insertData(PREPOPULATE_DATA)
                                }
                            }
                        })
                        .build()

        val PREPOPULATE_DATA = listOf(Data("1", "val"), Data("2", "val 2"))
    }
}

但是,从代码中可以看出,最终,数据(这里,val PREPOPULATE_DATA)是在代码中重新创建的。 (在另一个代码中,使用了db.execSQL()

这样和最后从资源文件中取数据没有区别

有什么好的方法吗?

开发人员文档使用 assetsfiles

不过据说In-memory Room databases内不支持。

在这种情况下,我不知道In-memory是什么意思,所以我没有使用它。

In this case, I do not know what In-memory means, so I am not using it.

In-Memory 将是一个非持久性数据库,即数据库是在内存中使用而不是作为文件创建的,有时它会被删除。您可能不想要 in-memory 数据库。

However, as you can see from the code, in the end, data (here, val PREPOPULATE_DATA) is being created again within the code. (In another code, db.execSQL() is used)

这是编写 App 时的一个常见误解,因为 activity 的 onCreate 方法经常在 App 运行ning 时重复出现。对于 SQLite 数据库,数据库在其生命周期内只创建一次,即从 App 第一次 运行 开始直到数据库文件被删除。否则数据库将保留(即使在应用程序版本更改之间)。

Is there any good way?

pre-populated 数据库基本上有两个选项。他们是

  1. 添加数据 when/after 创建数据库,如您的示例代码 (这不是一个很好的示例,如下所述),或
  2. 利用 pre-packaged 数据库,即在应用程序外部创建的数据库(通常使用 SQlite 工具,例如 DBeaver、Navicat for SQlite、SQLiteStudio、DB Browser for SQLite)。

选项 1 - 添加数据

如果数据只应添加一次,则通过 CallBack 使用覆盖的 onCreate 方法可以使用。但是,不应使用注释为 class(es) 的 @Dao 中的 functions/methods。相反,应该只使用 SupportSQLiteDatabase functions/methods,例如execSQL(因此将 SupportSQLiteDatabase 传递给 onCreate)。

这是因为在那个阶段数据库刚刚创建,所有的底层处理还没有完成。

通过使用 INSERT OR IGNORE .... 而不是 INSERT ....,您可以很容易地防止重复数据。如果存在适用的约束违规(违反规则),这将跳过插入。因此,它依赖于此类规则的生效。

两个最常用的约束是 NOT NULLUNIQUE,后者隐式用于主键。

  • 在您的情况下,如果 Data 对象只有 2 个字段(数据库术语中的列),那么由于 Room 需要主键,因此应用隐式 UNIQUE 约束(可以是列或两者的复合主键)。因此,第二次添加 Data(1,"val") 将导致违反约束,这将导致
  1. 正在删除的行和插入的另一行(如果插入或替换)
    1. autogenerate 的值进一步复杂化了。
  2. 违规导致异常。
  3. 如果使用了 INSERT OR IGNORE,则跳过插入。

此选项可能适用于少量数据,但如果过度使用可能会开始使代码膨胀并导致其可维护性受到损害。

  • 如果使用 INSERT or IGNORE(或替代检查),那么这甚至可以在回调的 onOpen 方法中进行,但需要一些额外的开销。每次打开数据库时都会调用它。

Pre-packaged 数据库

如果您有大量初始数据,则在外部创建数据库,将其作为资产包括在内(因此它是部署包的一部分),然后使用 Room 的 .createFromAsset (或较少使用的 .createFromFile 将是正确的选择。

然而,这样做的缺点是 Room 期望这样的数据库符合它确定的模式,并且这些期望非常严格。因此,只是在不了解 Room 的细微差别的情况下组装一个数据库,那可能是一场噩梦。

  • 例如SQLite 的灵活性允许列类型几乎可以是任何类型(参见 )。 Room 仅允许 INTEGER、TEXT、REAL 或 BLOB 列类型。任何其他结果都是 Expected .... Found ... 消息的异常。

但是,解决此问题的简单方法是让 Room 告诉您它期望的模式是什么。为此,您创建带注释的@Entity classes(表),创建带注释的@Database class,包括实体参数中的各个实体,然后编译。在 Android Studio 的 Android 视图中 java(generated) 将在资源管理器中可见。其中将有一个 class 与注释为 class 的 @Database 同名,但后缀为 _Impl。在这个 class 中有一个 function/method createAllTables 并且它包括所有表 execSQL 语句(room_master_table 应该被忽略,因为 Room总是会自己创建).

数据库一旦创建并保存,应复制到资产文件夹中,然后使用 .createFromAsset(?????) 将导致 pre-packaged 数据从包到适当的本地存储位置。