0

我正在使用房间数据库。我有 2 张桌子 A 和 B。

A的主键是表B中的外键。

以下是我的数据库结构 -

@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)

现在我想从房间数据库中删除表 A。但是表 B 将在那里。那么我应该如何处理这种迁移呢?我特别注意删除 foreignKeys 约束。无法弄清楚。我认为应该在删除父表 A 之前将其删除。

表 B 中的某些列也将被删除。

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

  1. 删除外键约束(需要解决方案如何做到这一点?)
  2. 删除表 A
  3. 更改表 B

请在第 1 点指导我,并建议我是否有更好的方法来处理这个问题。

谢谢。

4

1 回答 1

1

首先基于表 B 创建一个表,例如

 DROP TABLE IF EXISTS B_temp;
 CREATE TABLE B_temp AS SELECT * FROM B;

然后从表 B 中删除所有行,例如

DELETE FROM B;

然后 DROP(或稍后重命名和 DROP)表 A;

DROP TABLE IF EXISTS A;

对表 B 进行更改。

然后使用 B_temp 表中的数据重新加载表 B,例如

INSERT INTO B SELECT * FROM B_temp;

最后删除 B_temp 表;

另一种方法是使用SupportSQliteDatabase's setForeignKeyConstraintsEnabledbefore (false) 和 after (true)。在这种情况下,不会强制执行外键,因此您可以只执行您指定的步骤。

使用方法 1 的示例

以下是基于您问题中的代码的工作示例。

迁移前 - 带有一些数据的版本 1。

@Dao 注解的AllDao抽象类:-

@Dao 抽象类 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>

}

版本 1 和 2的 @Database 注释类TheDatabase :-

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")
            }
        }
    }
}
  • 注意新表B的 CREATE TABLE是在对版本 2 进行更改时在编译后从生成的 java 中复制的。

  • 为了方便和简洁,在主线程上运行。

一个活动MainActivity,在版本 1 时,将一些数据加载到表 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 创建的表
  • 如果任何表包含 AUTOINCREMENT 关键字,则 sqlite_sequence 是创建的表
  • room_master_table 是由 Room 创建的,它存储了根据 schema 生成的 hash,用于检测 schema 的变化。

和:-

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

移民

B类更改为没有将其绑定到表 * 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>
}

数据库更改为版本 2 并排除表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)
  • 可以看出,没有表A和表B已相应更改(不是外键约束)

并且数据已被保留:-

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
于 2022-03-02T20:02:56.483 回答