Room Database Migration 无法正确处理 ALTER TABLE 迁移

Posted

技术标签:

【中文标题】Room Database Migration 无法正确处理 ALTER TABLE 迁移【英文标题】:Room Database Migration doesnt properly handle ALTER TABLE migration 【发布时间】:2018-03-04 11:09:51 【问题描述】:

Java.lang.IllegalStateException

没有正确处理迁移 用户(therealandroid.github.com.roomcore.java.User)。

预期:

TableInfoname='user', columns=name=Columnname='name', type='TEXT', notNull=false, primaryKeyPosition=0, age=Columnname='age', 类型='整数',notNull=true,primaryKeyPosition=0, id=Columnname='id', type='INTEGER', notNull=true, primaryKeyPosition=1,foreignKeys=[] 找到:

找到

TableInfo name='user', columns=name=Columnname='name', type='TEXT', notNull=false, primaryKeyPosition=0, id=Columnname='id', 类型='整数',notNull=true,primaryKeyPosition=1, age=Columnname='age', type='INTEGER', notNull=false, primaryKeyPosition=0,foreignKeys=[]

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

这里是代码

用户类

@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;
  

数据库类

@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

其他信息:

Android Studio 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

有人可以帮帮我吗,我真的不知道我做错了什么或者这是一个错误。

【问题讨论】:

在黑暗中尝试一下,但不妨试试"ALTER TABLE 'user' ADD COLUMN 'age' INTEGER NOT NULL DEFAULT 0"(0 可能是您认为合适的任何值)。 Room 期望列顺序与字段顺序相匹配。 ALTER TABLE 的结果似乎导致了不同的顺序。 检查下面的答案。它有完整的描述:***.com/a/51245898/3073945 【参考方案1】:

我在使用 roomVersion '2.4.0-alpha01' 时遇到问题,此版本不会为我的案例生成索引表

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

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

【讨论】:

【参考方案2】:

@HRankit 在他的回答中提到的工具:link 今天尝试时似乎不起作用。如果您也遇到这种情况,请继续阅读:

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

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

最后,您应该检查迁移本身的 sql 语句。

【讨论】:

【参考方案3】:

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

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

【讨论】:

【参考方案4】:

我在 kotlin 中遇到了 notNull 差异,发现了如下异常

Expected:
TableInfoname='enDic', columns=definition=Columnname='definition', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null', _id=Columnname='_id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null', favourite=Columnname='favourite', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null', word=Columnname='word', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null', usage=Columnname='usage', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null', foreignKeys=[], indices=[]
Found:
TableInfoname='enDic', columns=usage=Columnname='usage', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null', definition=Columnname='definition', type='text', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null', _id=Columnname='_id', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null', favourite=Columnname='favourite', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null', word=Columnname='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

)

【讨论】:

【参考方案5】:

如果你得到 notNull 差异,你可以简单地用 @NonNull 注释标记你的类字段,或者用 ALTER TABLE 改变你的 sql。但是,如果您遇到列类型差异,例如预期:TYPE=TEXT,然后找到 TYPE='' (COLLATE NOCASE),或预期 INTEGER,找到 INT,那么唯一的解决方案是删除并重新创建您的表。 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");

        
    ;

【讨论】:

【参考方案6】:

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

【讨论】:

奇怪地引起了error: incomparable types: int and &lt;null&gt;的单独问题 为我工作。谢谢【参考方案7】:

任何链接中的答案都不正确。经过多次试验,终于找到了办法。 ALTER 查询需要按以下方式编写才能使其工作:

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

但是,整数 DEFAULT 值可以是任何值。

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

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

这就像一个魅力。

【讨论】:

我可以给字符串列添加默认值吗?【参考方案8】:

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

过程非常简单。

    在Expected列中输入Expected错误日志,即Left One。

    在 Found 栏中输入 Found 错误日志,即正确的。

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

    按下比较按钮,瞧,你有你需要的差异。

此插件从 Android Studio Logcat 中找出两个 Expected 和 Found 转储的区别。

在此处查看比较图像

【讨论】:

虽然这在理论上可以回答这个问题,it would be preferable 在此处包含答案的基本部分,并提供链接以供参考。 @HRankit 太棒了,你的脚本解决了我的问题。 这个工具现在不能用了,你能重新检查一下吗? @RiteshAdulkar 如果现有的 sqlite 表有 VARCHAR 列而新表有 TEXT 怎么办。迁移时会有所不同吗? 嗨@HRankit 我很欣赏这个工具,但我无法让它看到我的粘贴是合法的。它被踢出与我应该以特定格式粘贴的相同错误消息。粘贴失败时能否提供更多调试信息?【参考方案9】:

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

TableInfoname='user', columns=name=Columnname='name', type='TEXT', notNull=false, primaryKeyPosition=0, age=Columnname='age', type ='INTEGER', notNull=true, primaryKeyPosition=0, id=Columnname='id', type='INTEGER', notNull=true, primaryKeyPosition= 1,foreignKeys=[] 找到:

找到

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

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

将您的迁移更改为:

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

由于这个异常解释非常难以解析,我创建了a small script 来为你做差异。

例子:

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

结果:

【讨论】:

像魅力一样工作,感谢您的帮助。现在我明白了,当您执行迁移时,您必须为要填充的新列指定默认值。 在虚拟数据库上尝试迁移,看看查询是否符合您的预期。 我还发现了一些有趣的事情:如果您在模型中的 @Entity 注释中创建了索引,您还需要在迁移脚本的 SQL 中执行此操作,例如 CREATE INDEX index_History_nodeId` ON @987654327 @ (nodeId)`,如果你不这样做,你会得到一个Migration didn't properly handle 您好,我收到了 Expected type='TEXT' 和 Found type='nvarchar' 的错误。如何在 Java 中表示 type='nvarchar'? 请在alter查询的末尾为添加的列添加默认的“some_default_value”,否则您会得到其他与迁移相关的异常,例如“无法添加默认值为NULL的NOT NULL列”

以上是关于Room Database Migration 无法正确处理 ALTER TABLE 迁移的主要内容,如果未能解决你的问题,请参考以下文章

Oracle Cloud Database MIgration and Integration 认证备考

Oracle Cloud Database MIgration and Integration 认证备考

在通过“update-database”命令更新数据库后,有啥方法可以恢复 Ef Core - Database Migration [重复]

Laravel Migration致命错误:调用未定义的方法Illuminate Database Schema Blueprint :: integer()

Android Room Database 学习

Android Room Database:如何处理实体中的 Arraylist?