房间数据库迁移没有正确处理 ALTER TABLE 迁移

Room Database Migration doesnt properly handle ALTER TABLE migration

Java.lang.IllegalStateException

Migration didn't properly handle user(therealandroid.github.com.roomcore.java.User).

Expected:

TableInfo{name='user', columns={name=Column{name='name', type='TEXT', notNull=false, primaryKeyPosition=0}, age=Column{name='age', type='INTEGER', notNull=true, primaryKeyPosition=0}, id=Column{name='id', type='INTEGER', notNull=true, primaryKeyPosition=1}}, foreignKeys=[]} Found:

Found

TableInfo{ name='user', columns={name=Column{name='name', type='TEXT', notNull=false, primaryKeyPosition=0}, id=Column{name='id', type='INTEGER', notNull=true, primaryKeyPosition=1}, age=Column{name='age', type='INTEGER', notNull=false, primaryKeyPosition=0}}, foreignKeys=[]}

我正在尝试执行一个简单的迁移,我有一个名为 User 的 class,它有两列 ID (primary key)NAME TEXT,然后我填充数据库有两个用户数据,然后我在对象 User 中添加列 AGE 并在迁移常量中添加一个 alter table 来添加这个新列,最后我替换数据库版本 1到 2.

这是代码

User.class

@Entity(tableName = "user")
  public class User {

  @PrimaryKey
  private int id;

  @ColumnInfo(name = "name")
  private String name;

  @ColumnInfo(name = "age")
  private int age;


  public int getId() {
      return id;
  }

  public void setId(int id) {
      this.id = id;
  }

  public String getName() {
      return name;
  }

  public void setName(String name) {
      this.name = name;
  }

  public int getAge() {
      return age;
  }

  public void setAge(int age) {
      this.age = age;
  }
}

数据库class

@Database(entities = {User.class}, version = 2)
public abstract class RoomDatabaseImpl extends RoomDatabase {
    abstract UserDao userDao();
}

迁移代码

public static Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE 'user' ADD COLUMN 'age' INTEGER");
    }
 };

它调用

Room.databaseBuilder(context, RoomDatabaseImpl.class, "Sample.db")
            .addMigrations(MIGRATION_1_2)
            .allowMainThreadQueries()
            .build();

在更改对象之前添加 AGE 并执行迁移我添加了两个寄存器并且它起作用了。

执行迁移后,我只是尝试添加一个新用户,如下所示:

  User user = new User();
  user.setName("JoooJ");
  user.setId(3);
  user.setAge(18);

  List<User> userList = new ArrayList<>();
  userList.add(user);
  App.database(this).userDao().insertAll(userList);  // The crash happens here

其他信息:

AndroidStudio 3我没有实际测试过

依赖关系:

compile "android.arch.persistence.room:runtime:1.0.0-alpha9-1"
annotationProcessor "android.arch.persistence.room:compiler:1.0.0-alpha9-1"

compile "android.arch.persistence.room:rxjava2:1.0.0-alpha9-1"
gradle 2.3.3

谁能帮帮我,我真的不知道我做错了什么,或者这是一个错误。

错误信息很难解析,但还是有区别的:

TableInfo{name='user', columns={name=Column{name='name', type='TEXT', notNull=false, primaryKeyPosition=0}, age=Column{name='age', type='INTEGER', notNull=true, primaryKeyPosition=0}, id=Column{name='id', type='INTEGER', notNull=true, primaryKeyPosition=1}}, foreignKeys=[]} Found:

找到

TableInfo{ name='user', columns={name=Column{name='name', type='TEXT', notNull=false, primaryKeyPosition=0}, id=Column{name='id', type='INTEGER', notNull=true, primaryKeyPosition=1}, age=Column{name='age', type='INTEGER', notNull=false, primaryKeyPosition=0}}, foreignKeys=[]}

年龄可以为空,但 Room 期望它不为空。

将迁移更改为:

database.execSQL("ALTER TABLE 'user' ADD COLUMN 'age' INTEGER NOT NULL");

由于此异常解释非常难以解析,因此我创建了 a small script 来为您区分。

示例:

mig "java.lang.IllegalStateException: Migration failed. expected:TableInfo{name='user', columns={name=Column{name='name', type='TEXT', notNull=false, primaryKeyPosition=0}, age=Column{name='age', type='INTEGER', notNull=true, primaryKeyPosition=0}, id=Column{name='id', type='INTEGER', notNull=true, primaryKeyPosition=1}}, foreignKeys=[]} , found:TableInfo{name='user', columns={name=Column{name='name', type='TEXT', notNull=false, primaryKeyPosition=0}, id=Column{name='id', type='INTEGER', notNull=true, primaryKeyPosition=1}, age=Column{name='age', type='INTEGER', notNull=false, primaryKeyPosition=0}}, foreignKeys=[]}"

结果:

我也写了一个小的 JS 脚本,你可以找到它 https://hrankit.github.io/RoomSQLiteDifferenceFinder/

过程非常简单。

  1. 在左侧的Expected列中输入Expected error log

  2. 在 Found 列中输入 Found error log 是正确的。

  3. 按开始。按钮。错误日志被转换为 JSON.

  4. 按“比较”按钮,瞧,你有你需要的差异。

此插件找出来自 Android Studio Logcat 的两个 Expected 和 Found 转储中的差异。

在这里查看比较图像

如果您得到 notNull 差异,您可以简单地用 @NonNull 注释标记您的 class 字段,或使用 ALTER TABLE 更改您的 sql。但是,如果您遇到列类型差异,例如预期的:TYPE=TEXT,然后找到 TYPE=''(COLLATE NOCASE),或预期的 INTEGER,找到 INT,那么唯一的解决方案是删除并重新创建您的 table . Sqlite 不允许更改列类型。

在 Sqlite 中使用 INTEGER 而不是 INT,并使用 @ColumnInfo(collat​​e = NOCASE) 标记您的 Java 实体(如果您在 Sqlite 中使用 NOCASE)。

查看 app\schemas 下的 json 文件以获得预期查询的 sql。

static final Migration MIGRATION_2_3= new Migration(2, 3) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {

            database.execSQL("DROP TABLE IF EXISTS table_tmp");

            database.execSQL("CREATE TABLE IF NOT EXISTS `table_tmp` ...");

            database.execSQL("insert into table_tmp (`id`, `name` , ...");

            database.execSQL("DROP INDEX IF EXISTS `index_table_name`");

            database.execSQL("CREATE INDEX IF NOT EXISTS `index_table_name` ON `table_tmp` (`name`)");

            database.execSQL("DROP TABLE IF EXISTS table");

            database.execSQL("alter table table_tmp rename to table");

        }
    };

None 的答案在任何链接中都是正确的。经过多次实验,找到了一种方法。需要按以下方式编写 ALTER 查询才能使其工作:

database.execSQL("ALTER TABLE 'user' ADD COLUMN 'age' INTEGER NOT NULL DEFAULT 0")

但是,Integer DEFAULT 值可以是任何值。

如果要添加String类型的列,按以下方式添加:

database.execSQL("ALTER TABLE 'user' ADD COLUMN 'address' TEXT")

这很有魅力。

我今天遇到了这个问题,我只是在 Entities 中将 int 字段更改为 Integer。由于 int 不能为空,但 Integer 对象可以为空。

如果要添加整数类型的列,请添加此代码

database.execSQL("ALTER TABLE users"
                    + " ADD COLUMN year INTEGER NOT NULL DEFAULT 0 ")

我在 kotlin 中遇到过 notNull 差异并发现如下异常

Expected:
TableInfo{name='enDic', columns={definition=Column{name='definition', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, _id=Column{name='_id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}, favourite=Column{name='favourite', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, word=Column{name='word', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, usage=Column{name='usage', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
Found:
TableInfo{name='enDic', columns={usage=Column{name='usage', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, definition=Column{name='definition', type='text', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, _id=Column{name='_id', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}, favourite=Column{name='favourite', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, word=Column{name='word', type='text', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}

然后我使用下面提到的代码来解决这个问题

@Entity(tableName = "enDic")
data class Word(

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "_id")
    var _id: Int?,

    @ColumnInfo(name = "word")
    var word: String?,

    @ColumnInfo(name = "definition")
    var definition: String,

    @ColumnInfo(name = "favourite")
    var favourite: Int?,

    @ColumnInfo(name = "usage")
    var usage: Int?

)

代替此代码

@Entity(tableName = "enDic")
data class Word(

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "_id")
    var _id: Int,

    @ColumnInfo(name = "word")
    var word: String,

    @ColumnInfo(name = "definition")
    var definition: String,

    @ColumnInfo(name = "favourite")
    var favourite: Int,

    @ColumnInfo(name = "usage")
    var usage: Int

)

@HRankit在他的回答中提到的工具:link今天试了一下好像没有用。如果您也是这种情况,请继续阅读:

如果有人正在努力解决问题中提到的错误消息并且有一个巨大的 table 包含很多列,您可能想尝试 this 在线工具来检查预期和找到的架构。

在生成 <db_version_number>.json(例如:13.json)文件之前决定列名和数据类型也非常重要。如果 json 文件已经生成并且您之后对实体 class 进行了一些更改,您可能想要删除 json 文件并重建项目以使用正确的值集生成它.

最后,您应该检查有关迁移本身的 sql 声明。

我遇到了房间版本“2.4.0-alpha01”的问题,这个版本没有为我的案例生成索引table

@Entity(
    tableName = "SoundRules",
    indices = [
        Index(value = ["remoteId"], unique = true)
    ]
) 

我解决了问题,刚刚将房间版本更新为“2.4.0-alpha03”