【问题标题】:Room database migration if only new table is added仅添加新表时的房间数据库迁移
【发布时间】:2018-07-02 03:54:13
【问题描述】:

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

【问题讨论】:

  • 您找到解决方案了吗?
  • 我遇到了同样的问题,并以与您相同的方式修复了它,但也没有找到解决方案。很高兴那时我并不孤单。 :)
  • 这里也一样。我发现 Room 能够在 database_impl 中生成创建查询,但是一旦迁移开始就不能只创建表,这很不方便......
  • 我愿意为这样的功能付出这么多...混合迁移和回退机制也很好...
  • 我不确定这是否会有所帮助,但 Room 确实可以选择将数据库模式导出到 JSON 文件中。 developer.android.com/training/data-storage/room/… 显然,这仍然意味着手动添加迁移脚本,但您不需要通过自动生成的类来获取 SQL 语句。

标签: java android database-migration android-room


【解决方案1】:

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

【讨论】:

  • 不,在这种情况下,房间会抛出日志:java.lang.IllegalStateException:需要从 {old_version} 迁移到 {new_version}
【解决方案2】:

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

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

【讨论】:

    【解决方案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()
    

    参考 - For more

    【讨论】:

      【解决方案4】:

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

      例子:

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

      别忘了更改数据库版本。

      【讨论】:

      • 此解决方案将从现有表中删除我的所有数据。
      • 不幸的是,是的。 “你可以调用这个方法来改变这个行为来重新创建数据库而不是崩溃。注意这会删除Room管理的数据库表中的所有数据。”
      【解决方案5】:

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

      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 语句。

      【讨论】:

        【解决方案6】:

        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 注释的类为例。这些是您必须遵循的步骤

        1. 创建一个新的StringBuilder 并附加“如果不存在则创建表”
        2. class.simplenametableName@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 for better Migration

        Application that uses RoomExtension

        希望有用。

        更新

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

        预计2.2.0会有更好的迁移系统

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

        【讨论】:

        • 你能指出你在哪里读到 Room 2.2.x 会有更好的迁移吗?我找不到任何可以证明这一点的东西,而且由于我们目前正在开发 2.1.0 测试版,因此目前似乎不知道 2.2.0 中的内容。
        • @jkane001 我给一位房间开发人员发了电子邮件,得到了回复。没有关于 2.2.x 的此类公告(还没有?)
        • 目前在 2.2.2 版上,但仍然没有更好的迁移 :( 但是,这是一个很好的答案,为我节省了大量工作,因此 +1。
        • @musooff 我实际上认为添加表创建是可以的。从“createAllTables”函数中复制代码是最安全的方法。
        • 2.4 版终于来了——目前处于 alpha 阶段!
        【解决方案7】:

        对于仍在寻找此问题的解决方案的任何人,我有一些好消息。 Room 2.4.0 版开始支持自动迁移

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

        【讨论】:

          猜你喜欢
          • 2021-09-02
          • 1970-01-01
          • 2019-02-13
          • 1970-01-01
          • 2022-01-22
          • 1970-01-01
          • 2021-08-13
          • 2023-01-31
          • 2018-12-01
          相关资源
          最近更新 更多