Android room DB - 在数据库升级中处理外键并改变 table
Android room DB - Handling of foreignKeys in DB upgrade and Altering the table
我正在使用房间数据库。我有 2 tables A 和 B.
A的主键是TableB中的外键
下面是我的数据库结构-
@Entity(tableName = "A")
data class A(
@PrimaryKey
@NotNull
@ColumnInfo(name = "IDKEY")
var xID: String
)
@Entity(
tableName = "B",
foreignKeys = [ForeignKey(
entity = A::class,
parentColumns = ["IDKEY"],
childColumns = ["KEY"],
onDelete = NO_ACTION
)])
data class B(
@PrimaryKey(autoGenerate = true)
@NotNull
@ColumnInfo(name = "KEY")
var id: Int = 0,
@ColumnInfo(name = "IDKEY")
var parentIDId: String)
现在我想从房间数据库中删除 table A。但是 table B 会在那里。
那么我应该如何处理这种迁移呢?我特别注意删除 foreignKeys 约束。想不通。我认为应该在删除父 table A.
之前删除
Table B 中的某些列也将被删除。
这就是我正在考虑的方法 -
- 删除外键约束(需要有关如何执行此操作的解决方案?)
- 删除table一个
- 改变tableB
请在第 1 点指导我,并建议我是否有更好的方法来处理这个问题。
谢谢。
首先根据 table B 创建一个 table 例如
DROP TABLE IF EXISTS B_temp;
CREATE TABLE B_temp AS SELECT * FROM B;
然后删除 table B 中的所有行,例如
DELETE FROM B;
然后 DROP(或稍后重命名和 DROP)TABLE A;
DROP TABLE IF EXISTS A;
对 table B.
进行修改
然后使用 B_temp table 中的数据重新加载 table B,例如
INSERT INTO B SELECT * FROM B_temp;
终于放下了 B_temp table;
另一种方法是在 (false) 之前和之后 (true) 使用 SupportSQliteDatabase
的 setForeignKeyConstraintsEnabled
。在这种情况下,不会强制执行外键,因此您可以只执行指定的步骤。
使用方法 1 的示例
以下是基于您问题中的代码的工作示例。
Pre-Migration - 带有一些数据的版本 1。
@Dao注解AllDao摘要class:-
@道
摘要 class AllDao {
/* Will be commented out (deleted) for Version 2 */
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(a: A): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(b: B): Long
@Query("SELECT * FROM B")
abstract fun getAllFromB(): List<B>
}
An @Database 注解 class TheDatabase 用于版本 1 和 2 :-
const val DATABASE_VERSION = 1
@Database(entities = [A::class,/*<<<<< NOTE will be commented out for Version 2*/B::class], exportSchema = false, version = DATABASE_VERSION)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDao(): AllDao
companion object {
private var instance: TheDatabase? = null
fun getInstance(context: Context): TheDatabase {
if (instance == null) {
instance = Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
.allowMainThreadQueries()
.addMigrations(MIGRATION_1_2)
.build()
}
return instance as TheDatabase
}
private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("DROP TABLE IF EXISTS B_temp")
db.execSQL("CREATE TABLE IF NOT EXISTS B_temp AS SELECT * FROM B;")
db.execSQL("DELETE FROM B")
db.execSQL("DROP TABLE IF EXISTS A;")
db.execSQL("DROP TABLE IF EXISTS B")
db.execSQL("CREATE TABLE IF NOT EXISTS `B` (`KEY` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `IDKEY` TEXT NOT NULL)")
db.execSQL("INSERT INTO B SELECT * FROM B_temp")
}
}
}
}
注意 CREATE TABLE for the new table B 是从编译后生成的 java 当对版本 2 进行更改时。
运行 在主线程上为了方便和简洁。
一个 activity MainActivity 将在版本 1 时将一些数据加载到 tables A 和 B:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
@SuppressLint("Range")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
if (DATABASE_VERSION == 1) {
/* Will be commented out for Version 2 */
dao.insert(A("1"))
dao.insert(A("2"))
dao.insert(A("3"))
dao.insert(B(id = 1,parentIDId = "ID001"))
dao.insert(B(id = 2,parentIDId = "ID002"))
dao.insert(B(id = 3,parentIDId = "ID003"))
}
/* Write the schema to the log */
val suppdb = db.openHelper.writableDatabase
val csr = suppdb.query("SELECT * FROM sqlite_master")
while (csr.moveToNext()) {
Log.d("DBINFO",
"Component is ${csr.getString(csr.getColumnIndex("name"))} " +
"Type is ${csr.getString(csr.getColumnIndex("type"))} " +
"SQL is \n\t${csr.getString(csr.getColumnIndex("sql"))}")
}
/* Write the data in table B to the log */
for(b in dao.getAllFromB()) {
Log.d("DBINFO","KEY = ${b.id} IDKEY = ${b.parentIDId}")
}
}
}
结果 :-
2022-03-03 08:39:35.353 D/DBINFO: Component is android_metadata Type is table SQL is
CREATE TABLE android_metadata (locale TEXT)
2022-03-03 08:39:35.353 D/DBINFO: Component is A Type is table SQL is
CREATE TABLE `A` (`IDKEY` TEXT NOT NULL, PRIMARY KEY(`IDKEY`))
2022-03-03 08:39:35.353 D/DBINFO: Component is sqlite_autoindex_A_1 Type is index SQL is
null
2022-03-03 08:39:35.354 D/DBINFO: Component is B Type is table SQL is
CREATE TABLE `B` (`KEY` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `IDKEY` TEXT NOT NULL, FOREIGN KEY(`KEY`) REFERENCES `A`(`IDKEY`) ON UPDATE NO ACTION ON DELETE NO ACTION )
2022-03-03 08:39:35.354 D/DBINFO: Component is sqlite_sequence Type is table SQL is
CREATE TABLE sqlite_sequence(name,seq)
2022-03-03 08:39:35.354 D/DBINFO: Component is room_master_table Type is table SQL is
CREATE TABLE room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)
- android_metadata 是由 android SQLite API
创建的 table
- sqlite_sequence 是一个 table,如果任何 table 包含 AUTOINCREMENT 关键字
- room_master_table 由 Room 创建,它存储根据模式生成的哈希,用于检测更改的模式。
并且:-
2022-03-03 08:39:35.361 D/DBINFO: KEY = 1 IDKEY = ID001
2022-03-03 08:39:35.361 D/DBINFO: KEY = 2 IDKEY = ID002
2022-03-03 08:39:35.361 D/DBINFO: KEY = 3 IDKEY = ID003
迁移
Class B 更改为没有外键约束将其绑定到 table *A :-
@Entity(
tableName = "B"/*,
foreignKeys = [ForeignKey(
entity = A::class,
parentColumns = ["IDKEY"],
childColumns = ["KEY"],
onDelete = NO_ACTION
)]*/)
data class B(
@PrimaryKey(autoGenerate = true)
@NotNull
@ColumnInfo(name = "KEY")
var id: Int = 0,
@ColumnInfo(name = "IDKEY")
var parentIDId: String)
AllDao 必须更改为不引用故事 A 所以:-
@Dao
abstract class AllDao {
/* Will be commented out (deleted) for Version 2 */
/*
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(a: A): Long
*/
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(b: B): Long
@Query("SELECT * FROM B")
abstract fun getAllFromB(): List<B>
}
TheDatabase 更改为版本 2 并排除 table A :-
const val DATABASE_VERSION = 2 /*<<<<<<<<<< changed for version 2 */
@Database(entities = [/*A::class,*//*<<<<< NOTE will be commented out for Version 2*/B::class], exportSchema = false, version = DATABASE_VERSION)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDao(): AllDao
companion object {
private var instance: TheDatabase? = null
fun getInstance(context: Context): TheDatabase {
if (instance == null) {
instance = Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
.allowMainThreadQueries()
.addMigrations(MIGRATION_1_2)
.build()
}
return instance as TheDatabase
}
private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("DROP TABLE IF EXISTS B_temp")
db.execSQL("CREATE TABLE IF NOT EXISTS B_temp AS SELECT * FROM B;")
db.execSQL("DELETE FROM B")
db.execSQL("DROP TABLE IF EXISTS A;")
db.execSQL("DROP TABLE IF EXISTS B")
db.execSQL("CREATE TABLE IF NOT EXISTS `B` (`KEY` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `IDKEY` TEXT NOT NULL)")
db.execSQL("INSERT INTO B SELECT * FROM B_temp")
}
}
}
}
MainActivity 已更改,因此 A 对象未被引用:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
@SuppressLint("Range")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
if (DATABASE_VERSION == 1) {
/* Will be commented out for Version 2
dao.insert(A("1"))
dao.insert(A("2"))
dao.insert(A("3"))
*/
dao.insert(B(id = 1,parentIDId = "ID001"))
dao.insert(B(id = 2,parentIDId = "ID002"))
dao.insert(B(id = 3,parentIDId = "ID003"))
}
/* Write the schema to the log */
val suppdb = db.openHelper.writableDatabase
val csr = suppdb.query("SELECT * FROM sqlite_master")
while (csr.moveToNext()) {
Log.d("DBINFO",
"Component is ${csr.getString(csr.getColumnIndex("name"))} " +
"Type is ${csr.getString(csr.getColumnIndex("type"))} " +
"SQL is \n\t${csr.getString(csr.getColumnIndex("sql"))}")
}
/* Write the data in table B to the log */
for(b in dao.getAllFromB()) {
Log.d("DBINFO","KEY = ${b.id} IDKEY = ${b.parentIDId}")
}
}
}
结果
在 运行 以上更改之后,日志包括:-
2022-03-03 08:53:06.016 D/DBINFO: Component is android_metadata Type is table SQL is
CREATE TABLE android_metadata (locale TEXT)
2022-03-03 08:53:06.016 D/DBINFO: Component is sqlite_sequence Type is table SQL is
CREATE TABLE sqlite_sequence(name,seq)
2022-03-03 08:53:06.016 D/DBINFO: Component is room_master_table Type is table SQL is
CREATE TABLE room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)
2022-03-03 08:53:06.017 D/DBINFO: Component is B_temp Type is table SQL is
CREATE TABLE B_temp("KEY" INT,IDKEY TEXT)
2022-03-03 08:53:06.017 D/DBINFO: Component is B Type is table SQL is
CREATE TABLE `B` (`KEY` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `IDKEY` TEXT NOT NULL)
- 可以看出没有tableA和tableB也做了相应的改动(不是Foreign关键约束)
AND 数据已保存:-
2022-03-03 08:53:06.023 D/DBINFO: KEY = 1 IDKEY = ID001
2022-03-03 08:53:06.023 D/DBINFO: KEY = 2 IDKEY = ID002
2022-03-03 08:53:06.023 D/DBINFO: KEY = 3 IDKEY = ID003
我正在使用房间数据库。我有 2 tables A 和 B.
A的主键是TableB中的外键
下面是我的数据库结构-
@Entity(tableName = "A")
data class A(
@PrimaryKey
@NotNull
@ColumnInfo(name = "IDKEY")
var xID: String
)
@Entity(
tableName = "B",
foreignKeys = [ForeignKey(
entity = A::class,
parentColumns = ["IDKEY"],
childColumns = ["KEY"],
onDelete = NO_ACTION
)])
data class B(
@PrimaryKey(autoGenerate = true)
@NotNull
@ColumnInfo(name = "KEY")
var id: Int = 0,
@ColumnInfo(name = "IDKEY")
var parentIDId: String)
现在我想从房间数据库中删除 table A。但是 table B 会在那里。 那么我应该如何处理这种迁移呢?我特别注意删除 foreignKeys 约束。想不通。我认为应该在删除父 table A.
之前删除Table B 中的某些列也将被删除。
这就是我正在考虑的方法 -
- 删除外键约束(需要有关如何执行此操作的解决方案?)
- 删除table一个
- 改变tableB
请在第 1 点指导我,并建议我是否有更好的方法来处理这个问题。
谢谢。
首先根据 table B 创建一个 table 例如
DROP TABLE IF EXISTS B_temp;
CREATE TABLE B_temp AS SELECT * FROM B;
然后删除 table B 中的所有行,例如
DELETE FROM B;
然后 DROP(或稍后重命名和 DROP)TABLE A;
DROP TABLE IF EXISTS A;
对 table B.
进行修改然后使用 B_temp table 中的数据重新加载 table B,例如
INSERT INTO B SELECT * FROM B_temp;
终于放下了 B_temp table;
另一种方法是在 (false) 之前和之后 (true) 使用 SupportSQliteDatabase
的 setForeignKeyConstraintsEnabled
。在这种情况下,不会强制执行外键,因此您可以只执行指定的步骤。
使用方法 1 的示例
以下是基于您问题中的代码的工作示例。
Pre-Migration - 带有一些数据的版本 1。
@Dao注解AllDao摘要class:-
@道 摘要 class AllDao {
/* Will be commented out (deleted) for Version 2 */
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(a: A): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(b: B): Long
@Query("SELECT * FROM B")
abstract fun getAllFromB(): List<B>
}
An @Database 注解 class TheDatabase 用于版本 1 和 2 :-
const val DATABASE_VERSION = 1
@Database(entities = [A::class,/*<<<<< NOTE will be commented out for Version 2*/B::class], exportSchema = false, version = DATABASE_VERSION)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDao(): AllDao
companion object {
private var instance: TheDatabase? = null
fun getInstance(context: Context): TheDatabase {
if (instance == null) {
instance = Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
.allowMainThreadQueries()
.addMigrations(MIGRATION_1_2)
.build()
}
return instance as TheDatabase
}
private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("DROP TABLE IF EXISTS B_temp")
db.execSQL("CREATE TABLE IF NOT EXISTS B_temp AS SELECT * FROM B;")
db.execSQL("DELETE FROM B")
db.execSQL("DROP TABLE IF EXISTS A;")
db.execSQL("DROP TABLE IF EXISTS B")
db.execSQL("CREATE TABLE IF NOT EXISTS `B` (`KEY` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `IDKEY` TEXT NOT NULL)")
db.execSQL("INSERT INTO B SELECT * FROM B_temp")
}
}
}
}
注意 CREATE TABLE for the new table B 是从编译后生成的 java 当对版本 2 进行更改时。
运行 在主线程上为了方便和简洁。
一个 activity MainActivity 将在版本 1 时将一些数据加载到 tables A 和 B:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
@SuppressLint("Range")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
if (DATABASE_VERSION == 1) {
/* Will be commented out for Version 2 */
dao.insert(A("1"))
dao.insert(A("2"))
dao.insert(A("3"))
dao.insert(B(id = 1,parentIDId = "ID001"))
dao.insert(B(id = 2,parentIDId = "ID002"))
dao.insert(B(id = 3,parentIDId = "ID003"))
}
/* Write the schema to the log */
val suppdb = db.openHelper.writableDatabase
val csr = suppdb.query("SELECT * FROM sqlite_master")
while (csr.moveToNext()) {
Log.d("DBINFO",
"Component is ${csr.getString(csr.getColumnIndex("name"))} " +
"Type is ${csr.getString(csr.getColumnIndex("type"))} " +
"SQL is \n\t${csr.getString(csr.getColumnIndex("sql"))}")
}
/* Write the data in table B to the log */
for(b in dao.getAllFromB()) {
Log.d("DBINFO","KEY = ${b.id} IDKEY = ${b.parentIDId}")
}
}
}
结果 :-
2022-03-03 08:39:35.353 D/DBINFO: Component is android_metadata Type is table SQL is
CREATE TABLE android_metadata (locale TEXT)
2022-03-03 08:39:35.353 D/DBINFO: Component is A Type is table SQL is
CREATE TABLE `A` (`IDKEY` TEXT NOT NULL, PRIMARY KEY(`IDKEY`))
2022-03-03 08:39:35.353 D/DBINFO: Component is sqlite_autoindex_A_1 Type is index SQL is
null
2022-03-03 08:39:35.354 D/DBINFO: Component is B Type is table SQL is
CREATE TABLE `B` (`KEY` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `IDKEY` TEXT NOT NULL, FOREIGN KEY(`KEY`) REFERENCES `A`(`IDKEY`) ON UPDATE NO ACTION ON DELETE NO ACTION )
2022-03-03 08:39:35.354 D/DBINFO: Component is sqlite_sequence Type is table SQL is
CREATE TABLE sqlite_sequence(name,seq)
2022-03-03 08:39:35.354 D/DBINFO: Component is room_master_table Type is table SQL is
CREATE TABLE room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)
- android_metadata 是由 android SQLite API 创建的 table
- sqlite_sequence 是一个 table,如果任何 table 包含 AUTOINCREMENT 关键字
- room_master_table 由 Room 创建,它存储根据模式生成的哈希,用于检测更改的模式。
并且:-
2022-03-03 08:39:35.361 D/DBINFO: KEY = 1 IDKEY = ID001
2022-03-03 08:39:35.361 D/DBINFO: KEY = 2 IDKEY = ID002
2022-03-03 08:39:35.361 D/DBINFO: KEY = 3 IDKEY = ID003
迁移
Class B 更改为没有外键约束将其绑定到 table *A :-
@Entity(
tableName = "B"/*,
foreignKeys = [ForeignKey(
entity = A::class,
parentColumns = ["IDKEY"],
childColumns = ["KEY"],
onDelete = NO_ACTION
)]*/)
data class B(
@PrimaryKey(autoGenerate = true)
@NotNull
@ColumnInfo(name = "KEY")
var id: Int = 0,
@ColumnInfo(name = "IDKEY")
var parentIDId: String)
AllDao 必须更改为不引用故事 A 所以:-
@Dao
abstract class AllDao {
/* Will be commented out (deleted) for Version 2 */
/*
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(a: A): Long
*/
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(b: B): Long
@Query("SELECT * FROM B")
abstract fun getAllFromB(): List<B>
}
TheDatabase 更改为版本 2 并排除 table A :-
const val DATABASE_VERSION = 2 /*<<<<<<<<<< changed for version 2 */
@Database(entities = [/*A::class,*//*<<<<< NOTE will be commented out for Version 2*/B::class], exportSchema = false, version = DATABASE_VERSION)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDao(): AllDao
companion object {
private var instance: TheDatabase? = null
fun getInstance(context: Context): TheDatabase {
if (instance == null) {
instance = Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
.allowMainThreadQueries()
.addMigrations(MIGRATION_1_2)
.build()
}
return instance as TheDatabase
}
private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("DROP TABLE IF EXISTS B_temp")
db.execSQL("CREATE TABLE IF NOT EXISTS B_temp AS SELECT * FROM B;")
db.execSQL("DELETE FROM B")
db.execSQL("DROP TABLE IF EXISTS A;")
db.execSQL("DROP TABLE IF EXISTS B")
db.execSQL("CREATE TABLE IF NOT EXISTS `B` (`KEY` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `IDKEY` TEXT NOT NULL)")
db.execSQL("INSERT INTO B SELECT * FROM B_temp")
}
}
}
}
MainActivity 已更改,因此 A 对象未被引用:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
@SuppressLint("Range")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
if (DATABASE_VERSION == 1) {
/* Will be commented out for Version 2
dao.insert(A("1"))
dao.insert(A("2"))
dao.insert(A("3"))
*/
dao.insert(B(id = 1,parentIDId = "ID001"))
dao.insert(B(id = 2,parentIDId = "ID002"))
dao.insert(B(id = 3,parentIDId = "ID003"))
}
/* Write the schema to the log */
val suppdb = db.openHelper.writableDatabase
val csr = suppdb.query("SELECT * FROM sqlite_master")
while (csr.moveToNext()) {
Log.d("DBINFO",
"Component is ${csr.getString(csr.getColumnIndex("name"))} " +
"Type is ${csr.getString(csr.getColumnIndex("type"))} " +
"SQL is \n\t${csr.getString(csr.getColumnIndex("sql"))}")
}
/* Write the data in table B to the log */
for(b in dao.getAllFromB()) {
Log.d("DBINFO","KEY = ${b.id} IDKEY = ${b.parentIDId}")
}
}
}
结果
在 运行 以上更改之后,日志包括:-
2022-03-03 08:53:06.016 D/DBINFO: Component is android_metadata Type is table SQL is
CREATE TABLE android_metadata (locale TEXT)
2022-03-03 08:53:06.016 D/DBINFO: Component is sqlite_sequence Type is table SQL is
CREATE TABLE sqlite_sequence(name,seq)
2022-03-03 08:53:06.016 D/DBINFO: Component is room_master_table Type is table SQL is
CREATE TABLE room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)
2022-03-03 08:53:06.017 D/DBINFO: Component is B_temp Type is table SQL is
CREATE TABLE B_temp("KEY" INT,IDKEY TEXT)
2022-03-03 08:53:06.017 D/DBINFO: Component is B Type is table SQL is
CREATE TABLE `B` (`KEY` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `IDKEY` TEXT NOT NULL)
- 可以看出没有tableA和tableB也做了相应的改动(不是Foreign关键约束)
AND 数据已保存:-
2022-03-03 08:53:06.023 D/DBINFO: KEY = 1 IDKEY = ID001
2022-03-03 08:53:06.023 D/DBINFO: KEY = 2 IDKEY = ID002
2022-03-03 08:53:06.023 D/DBINFO: KEY = 3 IDKEY = ID003