android - Room 2.1.0之后升级defaultValue迁移需求的困惑

标签 android android-room androidx

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

迁移指南来自

非常困惑。

Note: If your database schema already has default values, such as those added via ALTER TABLE x ADD COLUMN y INTEGER NOTNULL DEFAULT z, and you decide to define default values via @ColumnInfo to the same columns, then you might need to provide a migration to validate the unaccounted default values. See Room Migrations for more information.


升级到2.2.3之前,有2种可能

  1. 如果迁移运行,我们有一个具有默认值的 dummy0 列。
  2. 或者,如果这是新数据库,我们有一个没有默认值的 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 ''");
    }
}

仍然可以正常工作。


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

最佳答案

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

例如如果你改变了:-

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

添加默认值

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

那么就会出现模式不匹配,因为预期的模式将具有 DEFAULT '' 而找到的模式(原始数据库)没有默认编码。

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

如果在 2.2.0 之前你有一个包含默认值的非房间生成的模式并且实体没有相应地改变那么你会遇到冲突因为预期的模式没有默认值而找到的模式包含默认 = ''

  • 这需要相应地更改实体。

#例子

假设当前实体是:-

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

#修复示例

使用迁移:-

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

解决了这个问题。

#代码

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

密码.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

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

关于android - Room 2.1.0之后升级defaultValue迁移需求的困惑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59691979/

相关文章:

java - Android 文件夹结构(类,...)

java - 如何解决对对象列表进行排序时自定义比较器的意外行为?

android - android Room 数据库存储敏感数据安全吗?

android - 在 Android Room 中如何创建和调用自定义查询

java - 将自定义边框添加到警报对话框生成器布局

java - Android:每 X 小时从服务器更新一些天气数据

java - 是否有一个函数可以去除 HTTP 响应头?

java - 插入数据库时​​字符串转换奇怪

java - 意外隐式转换为字符串 : layout tag was EditText error

android - 使用共享 View 模型掌握细节流程