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 中的某些列也将被删除。

这就是我正在考虑的方法 -

  1. 删除外键约束(需要有关如何执行此操作的解决方案?)
  2. 删除table一个
  3. 改变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) 使用 SupportSQliteDatabasesetForeignKeyConstraintsEnabled。在这种情况下,不会强制执行外键,因此您可以只执行指定的步骤。

使用方法 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