如何在更新应用程序时向 android 房间数据库添加新数据?

How to add new data to android room database when updating app?

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

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

并且用户可以编辑它并在每个 table.

上插入新行

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

(我的问题的重点) 后来我希望 在最多 9999.

行中添加更多数据

我可以在用户更新应用程序时执行此操作吗? 或者还有其他方法吗?

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

谢谢!!

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

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

如果您有 @PrimaryKey(autogenerate = true) 以便用户启动,那么在准备原始预填充数据时,您可以轻松设置要使用的下一个用户 ID。

例如,如果实体是:-

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

即userid 和 userName 是列,当第一个 运行 您希望第一个 App 提供的 userid 为 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. 根据实体 创建 table(SQL 是从生成的 java @AppDatabase_Impl 复制而来)
  2. 加载一些用户
  3. 添加一个userId为9999(10000 - 1)的用户,这会导致SQLite在SQLite系统table sqlite_sequnce中记录9999用户 table.
  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 在 sqlite_sequence table 中为名为 user[ 的 table 存储了值 9999 =99=]

第二个查询显示添加第一个用户时发生的情况:-

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

添加新数据

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

这是一个如何管理它的例子。这使用更新的预填充数据并假设应用程序用户输入的现有数据将被保留。

一个重要的值是提供的用户 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的一些可能有用的,其他的用在class 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 class 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 第二版(另外 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 ...

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

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

Can I do this when users update the app? or is there any other way?

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

Maybe should I try to import csv file to room database?

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

can I use migration without changing the database schema?

是的,如上所示。