对 Room 2.1.0 后升级的 defaultValue 迁移要求感到困惑
Posted
技术标签:
【中文标题】对 Room 2.1.0 后升级的 defaultValue 迁移要求感到困惑【英文标题】:Confusion on the defaultValue migration requirement for upgrade after Room 2.1.0 【发布时间】:2020-04-28 17:05:01 【问题描述】:Room 2.1.0中,常见的代码如下
版本 2
@Entity(tableName = "password")
public class Password
@ColumnInfo(name = "dummy0")
@NonNull
public String dummy0;
public class Migration_1_2 extends Migration
public Migration_1_2()
super(1, 2);
@Override
public void migrate(@NonNull SupportSQLiteDatabase database)
database.execSQL("ALTER TABLE password ADD COLUMN dummy0 TEXT NOT NULL DEFAULT ''");
来自
的迁移指南 https://developer.android.com/training/data-storage/room/migrating-db-versions.md#handle-default-values-migrations https://developer.android.com/jetpack/androidx/releases/room相当混乱。
注意:如果您的数据库架构已经有默认值,例如 通过 ALTER TABLE x ADD COLUMN y INTEGER NOTNULL DEFAULT z 添加的那些, 并且您决定通过 @ColumnInfo 将默认值定义为相同的 列,那么您可能需要提供迁移以验证 未计算的默认值。有关详细信息,请参阅房间迁移。
升级到2.2.3之前,有2种可能
-
如果迁移运行,我们有一个带有默认值的
dummy0
列。
或者,如果这是新数据库,我们有一个没有默认值的 dummy0
列。
当我们将 Room 2.1.0 升级到 Room 2.2.3 时,这两种情况的一切仍然正常,无需添加额外的迁移代码,用于删除和重新创建表。
我们会做进一步的测试。
版本 3
@Entity(tableName = "password")
public class Password
@ColumnInfo(name = "dummy0")
@NonNull
public String dummy0;
@ColumnInfo(name = "dummy1")
@NonNull
public String dummy1;
public class Migration_2_3 extends Migration
public Migration_2_3()
super(2, 3);
@Override
public void migrate(@NonNull SupportSQLiteDatabase database)
database.execSQL("ALTER TABLE password ADD COLUMN dummy1 TEXT NOT NULL DEFAULT ''");
仍然可以正常工作。
版本 4
@Entity(tableName = "password")
public class Password
@ColumnInfo(name = "dummy0")
@NonNull
public String dummy0;
@ColumnInfo(name = "dummy1")
@NonNull
public String dummy1;
@ColumnInfo(name = "dummy2", defaultValue = "")
@NonNull
public String dummy2;
public class Migration_3_4 extends Migration
public Migration_3_4()
super(3, 4);
@Override
public void migrate(@NonNull SupportSQLiteDatabase database)
database.execSQL("ALTER TABLE password ADD COLUMN dummy2 TEXT NOT NULL DEFAULT ''");
仍然可以正常工作。
所以,我很困惑?在什么用例下,我们需要实际删除并重新创建表?
【问题讨论】:
【参考方案1】:我认为问题不在于添加新列时,而在于是否将默认值应用/更改/删除到现有列。那时您可能必须重新创建受影响的表。
例如如果你改变了:-
@ColumnInfo(name = "dummy0")
@NonNull
public String dummy0;
添加默认值
@ColumnInfo(name = "dummy0", defaultValue = "")
@NonNull
public String dummy0;
然后会出现架构不匹配,因为预期的架构将具有DEFAULT ''
,而找到的架构(原始数据库)没有默认编码。
如果在 2.2.0 之前,您有一个以前的非房间生成架构,其中包含默认值并且实体没有相应地更改,那么您会遇到冲突,因为预期架构没有默认值,而找到的架构包含DEFAULT = ''
.
示例
假设当前实体是:-
@Entity(tableName = "password")
public class Password
@PrimaryKey
public Long id;
@ColumnInfo(name = "dummy0")
@NonNull
public String dummy0;
@ColumnInfo(name = "dummy1")
@NonNull
public String dummy1;
那么生成的创建表的代码是:-
_db.execSQL("CREATE TABLE IF NOT EXISTS `password` (`id` INTEGER, `dummy0` TEXT NOT NULL, `dummy1` TEXT NOT NULL, PRIMARY KEY(`id`))");
应用已运行上述创建数据库。
如果现在版本 2 更改为:-
@Entity(tableName = "password")
public class Password
@PrimaryKey
public Long id;
@ColumnInfo(name = "dummy0", defaultValue = "" /*<<<<<<<<<< ADDED */)
@NonNull
public String dummy0;
@ColumnInfo(name = "dummy1")
@NonNull
public String dummy1;
那么生成的代码是:-
_db.execSQL("CREATE TABLE IF NOT EXISTS `password` (`id` INTEGER, `dummy0` TEXT NOT NULL DEFAULT '', `dummy1` TEXT NOT NULL, PRIMARY KEY(`id`))");
使用哑/空迁移 (1-2) 运行,然后:-
找到的模式(原始数据库)有:-defaultValue='null'
但预期的架构有:- defaultValue=''''
根据:-
2020-01-11 19:11:15.300 12539-12539/a.so59691979 E/AndroidRuntime: FATAL EXCEPTION: main
Process: a.so59691979, PID: 12539
java.lang.RuntimeException: Unable to start activity ComponentInfoa.so59691979/a.so59691979.MainActivity: java.lang.IllegalStateException: Migration didn't properly handle: password(a.so59691979.Password).
Expected:
TableInfoname='password', columns=dummy0=Columnname='dummy0', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='''', dummy1=Columnname='dummy1', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null', id=Columnname='id', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null', foreignKeys=[], indices=[]
Found:
TableInfoname='password', columns=dummy0=Columnname='dummy0', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null', dummy1=Columnname='dummy1', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null', id=Columnname='id', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null', foreignKeys=[], indices=[]
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3270)
示例修复
使用迁移:-
Migration M1_2 = new Migration(1,2)
@Override
public void migrate(@NonNull SupportSQLiteDatabase database)
// CREATE SQL Copied from the generated Java PasswordDatabase_Impl (name changed)
final String SQL_CREATE_NEW_PASSWORDTABLE =
"CREATE TABLE IF NOT EXISTS `password_new` (`id` INTEGER, `dummy0` TEXT NOT NULL DEFAULT '', `dummy1` TEXT NOT NULL, PRIMARY KEY(`id`))";
database.execSQL(SQL_CREATE_NEW_PASSWORDTABLE);
database.execSQL("INSERT INTO `password_new` SELECT * FROM `password`");
database.execSQL("ALTER TABLE `password` RENAME TO `password_old`");
database.execSQL("ALTER TABLE `password_new` RENAME TO `password`");
database.execSQL("DROP TABLE IF EXISTS `password_old`");
解决问题。
代码
以下代码用于生成上述内容:-
Password.java
/*
//Original
@Entity(tableName = "password")
public class Password
@PrimaryKey
public Long id;
@ColumnInfo(name = "dummy0")
@NonNull
public String dummy0;
@ColumnInfo(name = "dummy1")
@NonNull
public String dummy1;
*/
// New
@Entity(tableName = "password")
public class Password
@PrimaryKey
public Long id;
@ColumnInfo(name = "dummy0", defaultValue = "" /*<<<<<<<<<< ADDED */)
@NonNull
public String dummy0;
@ColumnInfo(name = "dummy1")
@NonNull
public String dummy1;
最初使用的是原版
PasswordDatabase.java
@Database(version = 2, entities = Password.class)
public abstract class PasswordDatabase extends RoomDatabase
初始版本为 1
MainActivity.java
公共类 MainActivity 扩展 AppCompatActivity
PasswordDatabase passwordDatabase;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
passwordDatabase = Room.databaseBuilder(
this,
PasswordDatabase.class,
"passworddb"
)
.allowMainThreadQueries()
.addMigrations(M1_2)
.build();
passwordDatabase.getOpenHelper().getWritableDatabase();
Migration M1_2 = new Migration(1,2)
@Override
public void migrate(@NonNull SupportSQLiteDatabase database)
// CREATE SQL Copied from the generated Java PasswordDatabase_Impl (name changed)
final String SQL_CREATE_NEW_PASSWORDTABLE =
"CREATE TABLE IF NOT EXISTS `password_new` (`id` INTEGER, `dummy0` TEXT NOT NULL DEFAULT '', `dummy1` TEXT NOT NULL, PRIMARY KEY(`id`))";
database.execSQL(SQL_CREATE_NEW_PASSWORDTABLE);
database.execSQL("INSERT INTO `password_new` SELECT * FROM `password`");
database.execSQL("ALTER TABLE `password` RENAME TO `password_old`");
database.execSQL("ALTER TABLE `password_new` RENAME TO `password`");
database.execSQL("DROP TABLE IF EXISTS `password_old`");
;
最初M1_2的主体是空的(以强制出错)
【讨论】:
以上是关于对 Room 2.1.0 后升级的 defaultValue 迁移要求感到困惑的主要内容,如果未能解决你的问题,请参考以下文章
添加 androidx.room:room-compiler:2.1.0-alpha05 后项目未编译
Android jetpack room 记录数据库升级日志