首页 > 解决方案 > 更新应用程序时如何将新数据添加到android房间数据库?

问题描述

我正在使用 Room 数据库制作一个 android 应用程序。

我的计划是在设备上安装时使用一些初始数据预填充数据库,

用户可以编辑它并在每个表上插入新行。

用户的新行 ID 将从例如 10000 开始,

(我的问题的重点),后来我想在最多 9999 的行中添加更多数据

当用户更新应用程序时我可以这样做吗?还是有其他方法?

也许我应该尝试将 csv 文件导入房间数据库

谢谢!!

我从应用资产预填充的代码

Room.databaseBuilder(application, AppDatabase::class.java, DB_NAME)
                 .createFromAsset("database/appdatabase.db")
                 .build()

标签: androiddatabaseandroid-sqliteandroid-roomandroid-room-prepackageddatabase

解决方案


为了让用户开始,如果你有@PrimaryKey(autogenerate = true)然后在准备原始预填充数据时,你可以轻松设置下一个要使用的用户 ID。

例如,如果实体是:-

@Entity
data class User(
    @PrimaryKey(autoGenerate = true)
    val userId: Long=0,
    val userName: String,
)

即 userid 和 userName 是列,当第一次运行时,您希望第一个 App 提供的用户 ID 为 10000,那么您可以在 SQLite 工具中使用(例如)以下内容:-

CREATE TABLE IF NOT EXISTS `User` (`userId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userName` TEXT);
INSERT INTO User (userName) VALUES('Fred'),('Mary'),('Sarah'); /* Add Users as required */
INSERT INTO User VALUES(10000 -1,'user to be dropped'); /* SETS the next userid value to be 10000 */
DELETE FROM user WHERE userid >= 10000 - 1; /* remove the row added */
  1. 根据Entity创建表(SQL是从生成的java @AppDatabase_Impl复制过来的)
  2. 加载一些用户
  3. 添加一个 userId 为 9999 (10000 - 1) 的用户,这会导致 SQLite 在 SQLite 系统表 sqlite_sequnce 中为用户表记录 9999。
  4. 删除为设置序号而添加的用户。

如果在上述之后使用以下内容,则演示执行上述操作的结果:-

/* JUST TO DEMONSTRATE WHAT THE ABOVE DOES */
/* SHOULD NOT BE RUN as the first App user is added */
SELECT * FROM sqlite_sequence;
INSERT INTO user (username) VALUES('TEST USER FOR DEMO DO NOT ADD ME WHEN PREPARING DATA');
SELECT * FROM user;

第一个查询:-

在此处输入图像描述

  • 即 SQLite 已将值9999存储在名为user的表的 sqlite_sequence 表中

第二个查询显示添加第一个用户时会发生什么:-

在此处输入图像描述

回顾一下,运行 1-4 准备预填充的数据库,以便第一个应用添加的用户的用户 ID 为 10000。

添加新数据

您确实必须决定如何添加新数据。你想要一个csv吗?您想提供更新的 AppDatabase 吗?使用所有数据还是仅使用新数据?您是否需要保留任何现有的用户/应用程序输入数据?新安装呢?细节很可能很重要。

这是一个如何管理此问题的示例。这使用更新的预填充数据,并假定应用程序用户输入的现有数据将被保留。

一个重要的值是提供的用户 ID 和通过正在使用的应用程序输入的用户 ID 之间的 10000 分界线。因此,已使用的用户实体是:-

@Entity
data class User(
    @PrimaryKey(autoGenerate = true)
    val userId: Long=0,
    val userName: String,
) {
    companion object {
        const val USER_DEMARCATION = 10000;
    }
}

一些 Dao 的一些可能有用,另一些在UserDao类中使用:-

@Dao
abstract class UserDao {
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    abstract fun insert(user: User): Long
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    abstract fun insert(users: List<User>): LongArray
    @Query("SELECT * FROM user")
    abstract fun getAllUsers(): List<User>
    @Query("SELECT * FROM user WHERE userid < ${User.USER_DEMARCATION}")
    abstract fun getOnlySuppliedUsers(): List<User>
    @Query("SELECT * FROM user WHERE userid >= ${User.USER_DEMARCATION}")
    abstract fun getOnlyUserInputUsers(): List<User>
    @Query("SELECT count(*) > 0 AS count FROM user WHERE userid >= ${User.USER_DEMARCATION}")
    abstract fun isAnyInputUsers(): Long
    @Query("SELECT max(userid) + 1 FROM user WHERE userId < ${User.USER_DEMARCATION}")
    abstract fun getNextSuppliedUserid(): Long
}

@Database 类AppDatabase:-

@Database(entities = [User::class],version = AppDatabase.DATABASE_VERSION, exportSchema = false)
abstract class AppDatabase: RoomDatabase() {
    abstract fun getUserDao(): UserDao

    companion object {
        const val DATABASE_NAME = "appdatabase.db"
        const val DATABASE_VERSION: Int = 2 /*<<<<<<<<<<*/

        private  var instance: AppDatabase? = null
        private var contextPassed: Context? = null
        fun getInstance(context: Context): AppDatabase {
            contextPassed = context
            if (instance == null) {
                instance = Room.databaseBuilder(
                    context,
                    AppDatabase::class.java,
                    DATABASE_NAME
                )
                    .allowMainThreadQueries()
                    .addMigrations(migration1_2)
                    .createFromAsset(DATABASE_NAME)
                    .build()
            }
            return instance as AppDatabase
        }
        val migration1_2 = object: Migration(1,2) {
            val assetFileName = "appdatabase.db" /* NOTE appdatabase.db not used to cater for testing */
            val tempDBName = "temp_" + assetFileName
            val bufferSize = 1024 * 4
            @SuppressLint("Range")
            override fun migrate(database: SupportSQLiteDatabase) {
                val asset = contextPassed?.assets?.open(assetFileName) /* Get the asset as an InputStream */
                val tempDBPath = contextPassed?.getDatabasePath(tempDBName) /* Deduce the file name to copy the database to */
                val os = tempDBPath?.outputStream() /* and get an OutputStream for the new version database */

                /* Copy the asset to the respective file (OutputStream) */
                val buffer = ByteArray(bufferSize)
                while (asset!!.read(buffer,0,bufferSize) > 0) {
                    os!!.write(buffer)
                }
                /* Flush and close the newly created database file */
                os!!.flush()
                os.close()
                /* Close the asset inputStream */
                asset.close()
                /* Open the new database */
                val version2db = SQLiteDatabase.openDatabase(tempDBPath.path,null,SQLiteDatabase.OPEN_READONLY)
                /* Grab all of the supplied rows */
                val v2csr = version2db.rawQuery("SELECT * FROM user WHERE userId < ${User.USER_DEMARCATION}",null)
                /* Insert into the actual database ignoring duplicates (by userId) */
                while (v2csr.moveToNext()) {
                    database.execSQL("INSERT OR IGNORE INTO user VALUES(${v2csr.getLong(v2csr.getColumnIndex("userId"))},'${v2csr.getString(v2csr.getColumnIndex("userName"))}')",)
                }
                /* close cursor and the newly created database */
                v2csr.close()
                version2db.close()
                tempDBPath.delete() /* Delete the temporary database file */
            }
        }
    }
  • 为了方便和简洁,已经在主线程上进行了测试,因此.allowMainThreadQueries

  • 可以看出,使用了从 1 到 2 的迁移:-

  • 获取资产 appdatabase.db 2nd 版本(另外 3 个“提供的”用户已添加“使用:-

    CREATE TABLE IF NOT EXISTS `User` (`userId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userName` TEXT NOT NULL);
    INSERT INTO User (userName) VALUES('Fred'),('Mary'),('Sarah'); /* Add Users as required */
    INSERT INTO User (userName) VALUES('Tom'),('Elaine'),('Jane'); /*+++++ Version 2 users +++++*/
    INSERT INTO User VALUES(10000 -1,'user to be dropped'); /* SETS the next userid value to be 10000 */
    DELETE FROM user WHERE userid >= 10000 - 1; /* remove the row added */```
    
    

所以首先资产 appdatabase.db 包含原始数据(3 个提供的用户)并且序列号设置为 9999。

如果应用程序具有数据库版本 1,则复制此预填充的数据库。

该应用程序的用户可以添加自己的用户ID,用户ID 将被分配10000、10001 ...

当下一个版本发布时,资产 appdatabase 会相应更改,保持 9999 序列号,忽略任何 App 输入用户 ID(它们是未知的),并且数据库版本从 1 更改为 2。

更新 App 时调用 migration1_2。如果新用户安装了应用程序,那么 Room 的 createFromAsset 会立即从资产中创建数据库。

当用户更新应用程序时我可以这样做吗?还是有其他方法?

如上所述,可以在更新应用程序并增加数据库版本时完成。可以通过其他方式完成,但检测更改的数据可能会变得复杂。

也许我应该尝试将 csv 文件导入房间数据库?

CSV 没有处理新安装和固有版本检查的优势。

我可以在不更改数据库架构的情况下使用迁移吗?

是的,如上所示。


推荐阅读