如果仅添加新表,则迁移会议室数据库 [英] Room database migration if only new table is added

查看:68
本文介绍了如果仅添加新表,则迁移会议室数据库的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

让我们假设我有一个简单的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 确实没有拥有良好的迁移系统,至少直到2.1.0-alpha03为止.

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

因为没有@Database(createNewTables = true)MigrationSystem.createTable(User::class)这样的方法,应该有一个或另一个方法,所以唯一可能的方法是运行

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

因此,您可以将上述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的房间注释创建一个注释处理库.以一个用@Entity注释的类为例.这些是您必须遵循的步骤

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

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

中将拥有更好的迁移系统

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

Let't assume, I have a simple Room database:

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

Now, I'm adding a new entity: Pet and bumping version to 2:

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

Of course, Room throws an exception: java.lang.IllegalStateException: A migration from 1 to 2 is necessary.

Assuming, I haven't changed User class (so all data is safe), I have to provide migration which just creates a new table. So, I'm looking into classes generated by Room, searching for generated query to create my new table, copying it and pasting into migration:

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

However I find it inconvenient to do it manually. Is there a way to tell Room: I'm not touching any of the existing table, so data is safe. Please create migration for me?

解决方案

Room does NOT have a good Migration System, at least not until 2.1.0-alpha03.

So, until we have better Migration System, there are some workarounds to have easy Migrations in the Room.

As there is no such method as @Database(createNewTables = true) or MigrationSystem.createTable(User::class), which there should be one or other, the only possible way is running

CREATE TABLE IF NOT EXISTS `User` (`id` INTEGER, PRIMARY KEY(`id`))

inside your migrate method.

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

In order to get above SQL script, you have 4 ways

1. Write by yourself

Basically, you have to write the above script that will match the script that Room generates. This way is possible, not feasible. (Consider you have 50 fields)

2. Export Schema

If you include exportSchema = true inside your @Database annotation, Room will generate database schema within /schemas of your project folder. The usage is

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

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

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

When you run or build the project you will get a JSON file 2.json, which has all the queries within your Room database.

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

So, you can include the above createSql within you migrate method.

3. Get query from AppDatabase_Impl

If you don't want to export schema you can still get the query by running or building the project which will generate AppDatabase_Impl.java file. and within the specified file you can have.

@Override
public void createAllTables(SupportSQLiteDatabase _db) {
  _db.execSQL("CREATE TABLE IF NOT EXISTS `User` (`id` INTEGER, PRIMARY KEY(`id`))");

Within createAllTables method, there will be the create scripts of all the entities. You can get it and include in within you migrate method.

4. Annotation Processing.

As you might guess, Room generates all of the above mentioned schema, and AppDatabase_Impl files within compilation time and with Annotation Processing which you add with

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

That means you can also do the same and make your own annotation processing library that generates all the necessary create queries for you.

The idea is to make an annotation processing library for Room annotations of @Entity and @Database. Take a class that is annotated with @Entity for example. These are the steps you will have to follow

  1. Make a new StringBuilder and append "CREATE TABLE IF NOT EXISTS "
  2. Get the table name either from class.simplename or by tableName field of @Entity. Add it to your StringBuilder
  3. Then for each field of your class create columns of SQL. Take the name, type, nullability of the field either by the field itself or by @ColumnInfo annotation. For every field, you have to add id INTEGER NOT NULL style of a column to your StringBuilder.
  4. Add primary keys by @PrimaryKey
  5. Add ForeignKey and Indices if exists.
  6. After finishing convert it to string and save it in some new class that you want to use. For example, save it like below

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

Then, you can use it as

val MIGRATION_1_2 = object : Migration(1, 2){
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL(UserSqlUtils().createTable)
    }
}

I made such a library for myself which you can check out, and even use it in your project. Note that the library that I made is not full and it just fulfills my requirements for table creation.

RoomExtension for better Migration

Application that uses RoomExtension

Hope it was useful.

UPDATE

By the time of writing this answer, room version was 2.1.0-alpha03 and when I emailed developers I got a response of

It is expected to have better Migration System in 2.2.0

Unfortunately, we still lack better Migration System.

这篇关于如果仅添加新表,则迁移会议室数据库的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆