如果仅添加新 table,则房间数据库迁移

Room database migration if only new table is added

假设我有一个简单的 Room 数据库:

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

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

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

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

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:我没有接触任何现有的 table,所以数据是安全的。请为我创建迁移?

也许在这种情况下(如果您只创建了新的 table 而没有更改其他内容)您可以这样做而不创建任何迁移吗?

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

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

你可以这样做-

@Database(entities = {User.class, Pet.class}, version = 2)

abstract class AppDatabase extends RoomDatabase {
public abstract Dao getDao();
public abstract Dao getPetDao();
}

剩下的就和你上面说的一样-

 db = Room.databaseBuilder(this, AppDatabase::class.java, "your_db")
        .addMigrations(MIGRATION_1_2).build()

参考 - For more

在这种情况下,您不需要进行迁移,您可以在创建数据库实例时调用.fallbackToDestructiveMigration()。

示例:

    instance = Room.databaseBuilder(context, AppDatabase.class, "database name").fallbackToDestructiveMigration().build();

并且不要忘记更改数据库版本。

您可以将以下 gradle 命令添加到 app.gradle 中的 defaultConfig:

javaCompileOptions {
        annotationProcessorOptions {
            arguments = ["room.schemaLocation":
                                 "$projectDir/schemas".toString()]
        }
    }

当您 运行 时,它将编译一个 table 名称列表及其相关的 CREATE TABLE 语句,您可以从中复制并粘贴到您的迁移对象中。您可能需要更改 table 个名称。

例如,这是我生成的模式:

"tableName": "assets",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `base` TEXT NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`asset_id`))"

所以我复制粘贴 createSql 语句并将“${TABLE_NAME}”更改为 'assets' table 名称,然后自动生成 Room create 语句。

Room 没有 好的迁移系统,至少在 2.1.0-alpha03.

之前没有

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

由于没有@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。导出架构

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

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

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

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

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

  "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
          },

因此,您可以在 migrate 方法中包含上述 createSql

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`))");

createAllTables方法中,会有所有实体的创建脚本。您可以获得它并将其包含在您的 migrate 方法中。

4。注释处理。

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

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

这意味着您也可以做同样的事情,制作您自己的注释处理库,为您生成所有必要的创建查询。

想法是为@Entity@Database的Room注解做一个注解处理库。以注释为 @Entity 的 class 为例。这些是您必须遵循的步骤

  1. 创建一个新的 StringBuilder 并追加 "CREATE TABLE IF NOT EXISTS "
  2. class.simplename@EntitytableName 字段获取 table 名称。将它添加到您的 StringBuilder
  3. 然后为 class 的每个字段创建 SQL 的列。通过字段本身或通过 @ColumnInfo 注释获取字段的名称、类型、可空性。 对于每个字段,您必须将列的 id INTEGER NOT NULL 样式添加到 StringBuilder.
  4. 通过@PrimaryKey
  5. 添加主键
  6. 添加 ForeignKeyIndices(如果存在)。
  7. 完成后将其转换为字符串并将其保存在您要使用的一些新的class中。比如像下面这样保存
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)
    }
}

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

RoomExtension for better Migration

Application that uses RoomExtension

希望有用。

更新

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

的回复

It is expected to have better Migration System in 2.2.0

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

对于仍在寻找此问题解决方案的任何人,我有一些好消息。 Room 从 2.4.0

版本开始支持自动迁移

https://developer.android.com/jetpack/androidx/releases/room#2.4.0-alpha01