对房间 2.1.0 之后升级的默认值迁移要求感到困惑



在 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

相当令人困惑。

注: 如果数据库模式已具有默认值,例如 那些通过 改变表 X 添加列 Y 整数 非空默认值 z, 并且您决定通过@ColumnInfo定义默认值 列,则可能需要提供迁移来验证 未计算的默认值。有关详细信息,请参阅房间迁移。


升级到 2.2.3 之前,有两种可能性

  1. 如果迁移运行,我们有一个默认值的dummy0列。
  2. 或者,如果这是新数据库,我们有一个没有默认值的dummy0列。

当我们升级到房间 2.1.0 到 房间 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 ''");
}
}

仍然工作正常。


所以,我很困惑?在什么用例下,我们需要实际删除并重新创建表?

我相信问题不在于添加新列,而在于是否将默认值应用/更改/删除到现有列。然后,您可能需要重新创建受影响的表。

例如,如果您更改了:-

@ColumnInfo(name = "dummy0")
@NonNull
public String dummy0;

添加默认值

@ColumnInfo(name = "dummy0", defaultValue = "")
@NonNull
public String dummy0;

然后会出现架构不匹配,因为预期的架构会DEFAULT '',而找到的架构(原始数据库(没有默认编码。

  • 这将需要删除并重新创建表,因为您无法 ALTER 来更改列的属性。

如果在 2.2.0 之前,您有一个包含默认值的先前非房间生成的架构,并且实体未相应地更改,则会出现冲突,因为预期的架构没有默认值,而找到的架构包含DEFAULT = ''

  • 这将要求对实体进行相应的更改。

#Example

假设当前实体是:-

@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 ComponentInfo{a.so59691979/a.so59691979.MainActivity}: java.lang.IllegalStateException: Migration didn't properly handle: password(a.so59691979.Password).
Expected:
TableInfo{name='password', columns={dummy0=Column{name='dummy0', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue=''''}, dummy1=Column{name='dummy1', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
Found:
TableInfo{name='password', columns={dummy0=Column{name='dummy0', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, dummy1=Column{name='dummy1', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3270)

#Example 修复

使用迁移:-

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

修复了此问题。

#Code

以下代码用于生成上述内容:-

密码.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;
}
  • 最初使用原始

密码数据库.java

@Database(version = 2, entities = {Password.class})
public abstract class PasswordDatabase extends RoomDatabase {
}
  • 初始版本为 1

主要活动.java

public class MainActivity extends 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的主体是空的(以便强制错误(

最新更新