房间数据库迁移没有正确处理 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/
过程非常简单。
在左侧的Expected列中输入Expected error log
在 Found 列中输入 Found error log 是正确的。
按开始。按钮。错误日志被转换为 JSON.
按“比较”按钮,瞧,你有你需要的差异。
此插件找出来自 Android Studio Logcat 的两个 Expected 和 Found 转储中的差异。
在这里查看比较图像
如果您得到 notNull 差异,您可以简单地用 @NonNull 注释标记您的 class 字段,或使用 ALTER TABLE 更改您的 sql。但是,如果您遇到列类型差异,例如预期的:TYPE=TEXT,然后找到 TYPE=''(COLLATE NOCASE),或预期的 INTEGER,找到 INT,那么唯一的解决方案是删除并重新创建您的 table . Sqlite 不允许更改列类型。
在 Sqlite 中使用 INTEGER 而不是 INT,并使用 @ColumnInfo(collate = 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”
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/
过程非常简单。
在左侧的Expected列中输入Expected error log
在 Found 列中输入 Found error log 是正确的。
按开始。按钮。错误日志被转换为 JSON.
按“比较”按钮,瞧,你有你需要的差异。
此插件找出来自 Android Studio Logcat 的两个 Expected 和 Found 转储中的差异。
在这里查看比较图像
如果您得到 notNull 差异,您可以简单地用 @NonNull 注释标记您的 class 字段,或使用 ALTER TABLE 更改您的 sql。但是,如果您遇到列类型差异,例如预期的:TYPE=TEXT,然后找到 TYPE=''(COLLATE NOCASE),或预期的 INTEGER,找到 INT,那么唯一的解决方案是删除并重新创建您的 table . Sqlite 不允许更改列类型。
在 Sqlite 中使用 INTEGER 而不是 INT,并使用 @ColumnInfo(collate = 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”