为了让用户开始,如果你有@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 */
- 根据Entity创建表(SQL是从生成的java @AppDatabase_Impl复制过来的)
- 加载一些用户
- 添加一个 userId 为 9999 (10000 - 1) 的用户,这会导致 SQLite 在 SQLite 系统表 sqlite_sequnce 中为用户表记录 9999。
- 删除为设置序号而添加的用户。
如果在上述之后使用以下内容,则演示执行上述操作的结果:-
/* 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 没有处理新安装和固有版本检查的优势。
我可以在不更改数据库架构的情况下使用迁移吗?
是的,如上所示。