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

时间:2018-01-23 10:50:31

标签: java android database-migration android-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:我没有触及现有的任何表,因此数据是安全的。请为我创建迁移?

7 个答案:

答案 0 :(得分:25)

房间确实具有没有的迁移系统,至少直到2.1.0-alpha03为止。

  

预计2.2.0的迁移系统会更好

因此,在我们拥有更好的迁移系统之前,有一些变通办法可以在会议室中轻松进行迁移。

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

如果在expportSchema = 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会在编译时以及您添加的注释处理中生成上述所有schemaAppDatabase_Impl文件

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

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

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

  1. 新建一个StringBuilder,并附加“如果不存在则创建表”
  2. class.simplenametableName的{​​{1}}字段中获取表名。将其添加到您的@Entity
  3. 然后为类的每个字段创建SQL列。通过字段本身或StringBuilder注释获取字段的名称,类型和可空性。 对于每个字段,必须将@ColumnInfo列的样式添加到id INTEGER NOT NULL中。
  4. 通过StringBuilder添加主键
  5. 添加@PrimaryKeyForeignKey(如果存在)。
  6. 完成后,将其转换为字符串并将其保存在您要使用的一些新类中。例如,如下保存

Indices


然后,您可以将其用作

public final class UserSqlUtils {
  public String createTable = "CREATE TABLE IF NOT EXISTS User (id INTEGER, PRIMARY KEY(id))";
}

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

RoomExtension for better Migration

Application that uses RoomExtension

希望它有用。

答案 1 :(得分:2)

对不起,Room不支持自动创建表格而不丢失数据。

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

答案 2 :(得分:1)

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

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

答案 3 :(得分:-1)

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

答案 4 :(得分:-1)

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

\

运行此命令时,它将编译表名称及其相关CREATE TABLE语句的列表,您可以从中复制并粘贴到迁移对象中。您可能必须更改表名。

例如,这是从我生成的模式中得出的:

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

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

答案 5 :(得分:-2)

您可以这样做-

@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

答案 6 :(得分:-4)

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

示例:

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

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