如果仅添加新表,则迁移会议室数据库


98

假设我有一个简单的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:我没有触摸任何现有表,因此数据是安全的。请为我创建迁移?


您找到解决方案了吗?
米克尔·拉尔森

3
我遇到了同样的问题,并以与您相同的方式解决了该问题,也没有找到解决方案。很高兴我并不孤单。:)
Mikkel Larsen

3
同样在这里。我发现房间能够在database_impl内部生成创建查询非常不便,但是一旦迁移开始,就不能仅创建表
...。– JacksOnF1re

1
对于这样的功能,我将付出很多……混合迁移和回退机制也将非常好……
Appyx

3
我不确定这是否会有所帮助,但Room可以选择将数据库架构导出到JSON文件。developer.android.com/training/data-storage/room/…显然,这仍然意味着手动添加迁移脚本,但是您无需遍历自动生成的类即可获取SQL语句。
詹姆斯·伦德雷姆

Answers:


75

确实具备良好的迁移系统,至少直到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脚本,您有四种方法

1.自己写

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

2.导出模式

如果您exportSchema = true@Database批注中加入,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
          },

因此,您可以包括以上内容 createSql在您的migrate方法中。

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会在编译时间内并使用您添加的Annotation Processing 生成上述所有文件schemaAppDatabase_Impl文件。

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

这意味着您也可以这样做,并创建自己的注释处理库,该库将为您生成所有必要的创建查询。

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

  1. 新建StringBuilder并追加“如果不存在则创建表”
  2. class.simplename或通过的tableName字段获取表名称@Entity。将其添加到您的StringBuilder
  3. 然后为类的每个字段创建SQL列。通过字段本身或@ColumnInfo注释来获取字段的名称,类型和可空性。对于每个字段,您都必须在其中添加id INTEGER NOT NULL列的样式StringBuilder
  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可实现更好的迁移

使用RoomExtension的应用程序

希望它有用。

更新

在撰写此答案时,会议室版本已定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

4
目前2.2.2版本,仍然没有更好的迁移:(然而,这是一个很好的答案,为我节省了大量的工作,以便+1了点。
smitty1

@androiddeveloper所有除#4,这是标注处理
musooff

1
@musooff我实际上认为可以添加表创建。这是从“ createAllTables”函数复制代码的最安全方法。
Android开发人员


0

你可以这样-

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

参考- 更多


0

您可以在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创建语句。


-1

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

例:

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

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


该解决方案将从现有表中删除所有数据。
Piotr Aleksander Chmielowski

不幸的是。“您可以调用此方法来更改此行为以重新创建数据库,而不是崩溃。请注意,这将删除Room管理的数据库表中的所有数据。”
rudicjovan

-2

也许在这种情况下(如果您仅创建新表而不更改其他表),您可以这样做根本不创建任何迁移吗?


1
否,在这种情况下,房间会抛出日志:java.lang.IllegalStateException:从{old_version}到{new_version}的迁移是必要的
Max Makeichik,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.