仅添加新表时的会议室数据库迁移

让我们假设,我有一个简单的房间数据库:

@Database(entities = {User.class}, version = 1)
abstract class AppDatabase extends RoomDatabase {
    public abstract Dao getDao();
}

现在,我正在添加一个新实体:并将版本提升到2:Pet

@Database(entities = {User.class, Pet.class}, version = 2)
abstract class AppDatabase extends RoomDatabase {
    public abstract Dao getDao();
}

当然,Room 会抛出一个异常:java.lang.IllegalStateException: A migration from 1 to 2 is necessary.

假设我没有更改类(所以所有数据都是安全的),我必须提供迁移,这只会创建一个新表。因此,我正在研究 Room 生成的类,搜索生成的查询以创建新表,将其复制并粘贴到迁移中:User

final Migration MIGRATION_1_2 =
        new Migration(1, 2) {
            @Override
            public void migrate(@NonNull final SupportSQLiteDatabase database) {
                database.execSQL("CREATE TABLE IF NOT EXISTS `Pet` (`name` TEXT NOT NULL, PRIMARY KEY(`name`))");
            }
        };

但是,我发现手动执行此操作不方便。有没有办法告诉Room:我没有触及任何现有的表格,所以数据是安全的。请为我创建迁移?


答案 1

房间没有一个好的迁移系统,至少在.2.1.0-alpha03

因此,在我们有更好的迁移系统之前,有一些解决方法可以在房间中轻松迁移。

由于没有 or 这样的方法,应该有一个或另一个,唯一可能的方法是运行@Database(createNewTables = true)MigrationSystem.createTable(User::class)

CREATE TABLE IF NOT EXISTS `User` (`id` INTEGER, PRIMARY KEY(`id`))

在你的方法里面。migrate

val MIGRATION_1_2 = object : Migration(1, 2){
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("CREATE TABLE IF NOT EXISTS `User` (`id` INTEGER, PRIMARY KEY(`id`))")
    }
}

为了超越SQL脚本,您有4种方法

1. 自己写

基本上,您必须编写与Room生成的脚本相匹配的上述脚本。这种方式是可能的,但不可行。(假设您有 50 个字段)

2. 导出架构

如果包含在批注中,Room 将在项目文件夹的 /schemas 中生成数据库架构。用法是exportSchema = true@Database

@Database(entities = [User::class], version = 2, exportSchema = true)
abstract class AppDatabase : RoomDatabase {
   //...
}

确保在应用模块中包含以下行build.grade

kapt {
    arguments {
        arg("room.schemaLocation", "$projectDir/schemas".toString())
    }
} 

当您运行或构建项目时,您将获得一个 JSON 文件 ,其中包含 Room 数据库中的所有查询。2.json

  "formatVersion": 1,
  "database": {
    "version": 2,
    "identityHash": "325bd539353db508c5248423a1c88c03",
    "entities": [
      {
        "tableName": "User",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, PRIMARY KEY(`id`))",
        "fields": [
          {
            "fieldPath": "id",
            "columnName": "id",
            "affinity": "INTEGER",
            "notNull": true
          },

因此,您可以在方法中包含上述内容。createSqlmigrate

3. 从AppDatabase_Impl获取查询

如果您不想导出架构,您仍然可以通过运行或构建将生成文件的项目来获取查询。并在您可以拥有的指定文件中。AppDatabase_Impl.java

@Override
public void createAllTables(SupportSQLiteDatabase _db) {
  _db.execSQL("CREATE TABLE IF NOT EXISTS `User` (`id` INTEGER, PRIMARY KEY(`id`))");

在方法中,将有所有实体的创建脚本。您可以获取它并包含在您的方法中。createAllTablesmigrate

4. 注释处理。

正如您可能猜到的那样,Room 会在编译时间内生成上述所有文件,并带有注释处理功能,您可以使用这些注释处理schemaAppDatabase_Impl

kapt "androidx.room:room-compiler:$room_version"

这意味着您也可以执行相同的操作,并创建自己的注释处理库,该库会为您生成所有必要的创建查询。

这个想法是为 和 的房间注释制作一个注释处理库。以一个带有注释的类为例。这些是您必须遵循的步骤@Entity@Database@Entity

  1. 新建并附加“如果不存在,则创建表”StringBuilder
  2. 从 或 按 字段获取表名。将其添加到您的class.simplenametableName@EntityStringBuilder
  3. 然后,对于类的每个字段,创建 SQL 列。通过字段本身或注释获取字段的名称、类型和可空性。对于每个字段,您必须向 .@ColumnInfoid INTEGER NOT NULLStringBuilder
  4. 通过以下方式添加主键@PrimaryKey
  5. 添加 和 (如果存在)。ForeignKeyIndices
  6. 完成后,将其转换为字符串并将其保存在要使用的某个新类中。例如,保存如下
public final class UserSqlUtils {
  public String createTable = "CREATE TABLE IF NOT EXISTS User (id INTEGER, PRIMARY KEY(id))";
}

然后,您可以将其用作

val MIGRATION_1_2 = object : Migration(1, 2){
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL(UserSqlUtils().createTable)
    }
}

我为自己制作了这样一个库,您可以查看,甚至可以在您的项目中使用它。请注意,我制作的库并不完整,它只是满足了我对表创建的要求。

房间扩展以实现更好的迁移

使用 RoomExtension 的应用程序

希望它有用。

更新

在撰写此答案时,房间版本是,当我向开发人员发送电子邮件时,我得到了回复2.1.0-alpha03

预计在以下方面将有更好的迁移系统2.2.0

不幸的是,我们仍然缺乏更好的迁移系统。


答案 2

抱歉,Room 不支持在不丢失数据的情况下自动创建表。

必须写入迁移。否则,它将擦除所有数据并创建新的表结构。


推荐