仅添加新表时的房间数据库迁移

147

假设我有一个简单的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类(因此所有数据都是安全的),我必须提供迁移,只需创建一个新表即可。因此,我查看由Room生成的类,寻找创建我的新表的生成查询,将其复制并粘贴到迁移中:

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


很遗憾,不行。 - Piotr Aleksander Chmielowski
4
我曾经遇到同样的问题,并像你一样解决了它,但是也没有找到任何解决办法。那么能够和我有相同经历感到很高兴 :) - Mikkel Larsen
3
我也有同感。我觉得在数据库实现中,Room 可以生成创建查询语句,但一旦迁移开始,就不能直接创建表格,这非常不方便。 - JacksOnF1re
1
我会为这样的功能付出很多... 混合迁移和回退机制也是不错的选择... - Appyx
3
我不确定这是否有帮助,但 Room 有将数据库模式导出为 JSON 文件的选项。https://developer.android.com/training/data-storage/room/migrating-db-versions.html#export-schema 显然,这仍意味着需要手动添加迁移脚本,但您无需通过自动生成的类来获取 SQL 语句。 - James Lendrem
显示剩余3条评论
7个回答

129

Room的迁移系统不太好,至少直到2.1.0-alpha03版本。

因此,在我们有更好的迁移系统之前,有一些解决方法可以让Room的迁移更容易。

由于没有@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中包含以下行:

Make sure that you have included below lines in build.grade of your app module


kapt {
    arguments {
        arg("room.schemaLocation", "$projectDir/schemas".toString())
    }
} 
当您运行或构建项目时,会得到一个名为2.json的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"
这意味着你也可以做同样的事情,创建你自己的注解处理库来为 Room 注解的 @Entity 和 @Database 生成所有必要的 create 查询语句。
这个想法是为 Room 注解的 @Entity 和 @Database 创建一个注解处理库。以被 @Entity 注解的类为例,以下是需要遵循的步骤:
1. 创建一个新的 StringBuilder 并添加 "CREATE TABLE IF NOT EXISTS " 2. 从 class.simplename 或者 @Entity 的 tableName 字段获取表名,并将它添加到你的 StringBuilder 中 3. 对于类的每个字段,根据字段本身或 @ColumnInfo 注解获取字段的名称、类型和可空性,为每个字段创建 SQL 列。对于每个字段,你需要将 id INTEGER NOT NULL 类型的列添加到你的 StringBuilder 中。 4. 通过 @PrimaryKey 添加主键。 5. 如果存在,则添加 ForeignKey 和 Indices。 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提供更好的迁移功能

使用RoomExtension的应用程序

希望这有用。

更新

在撰写本答案时,room版本为2.1.0-alpha03,当我给开发人员发送电子邮件时,我得到了以下回复:

预计在2.2.0中有更好的迁移系统

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


3
你可以指出在哪里阅读到了 Room 2.2.x 将具有更好迁移功能吗?我找不到任何支持这一说法的内容,而且由于我们目前正在处理 2.1.0 beta,所以目前还不清楚 2.2.0 中会包含什么。 - jkane001
1
@jkane001 我给其中一个房间开发者发了电子邮件,得到了回复。目前还没有关于2.2.x的公告(还没有吗?)。 - musooff
7
目前版本为2.2.2,但迁移仍然没有改善 :( 不过,这是一个很好的答案,省了我大量的工作,所以+1。 - jwitt98
@androiddeveloper 除了第四点,即注解处理(Annotation Processing)之外的所有内容。 - musooff
2
@musooff 我认为添加表格创建是可以的。这是从“createAllTables”函数复制代码的最安全方式。 - android developer
1
它终于要在2.4版本中发布了 - 目前处于alpha测试阶段! - JaviCasa

6

抱歉,Room不支持无数据损失的自动创建表格。

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


4

1
您可以在app.gradle文件的defaultConfig中添加以下Gradle命令:
javaCompileOptions {
        annotationProcessorOptions {
            arguments = ["room.schemaLocation":
                                 "$projectDir/schemas".toString()]
        }
    }

当您运行此代码时,它将编译一个表名列表及其相关的CREATE 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'表名,然后自动生成了Room create语句。


-3
你可以这样做 -
@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()

参考 - 了解更多


-3

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

示例:

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

别忘了更改数据库版本。


2
这个解决方案将从现有的表中删除所有我的数据。 - Piotr Aleksander Chmielowski
不幸的是,是的。您可以调用此方法来更改此行为,以重新创建数据库而不是崩溃。请注意,这将删除由Room管理的数据库表中的所有数据。 - rudicjovan

-3
也许在这种情况下(如果您只创建了新表而没有更改其他内容),您可以完全不创建任何迁移?

1
不,这种情况下会抛出日志:java.lang.IllegalStateException: 需要从{old_version}迁移到{new_version}。 - Max Makeichik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接