阅读 230

Room踩坑:理解Room的正确升库

前言

最近在用Room时候,由于很大意,错误的升级数据库。导致灰度期间出现了不少crash。这篇文章就来纪念一下自己的“年少无知”吧。

这篇文章翻译于Google的官方博客(自备科学上网),是我踩坑后搜到的,贴出来希望大家避免掉坑里吧~

正文

删除了一些原文中的“废话”,直接上处理代码。

有兴趣看更多细节的小伙伴,可以直接看原文。

前置条件,我们现在的app版本中已经建了这样的数据库:

@Database(entities = {User.class}, version = 1)
public abstract class UsersDatabase extends RoomDatabase
复制代码

如果我们在下一个版本的app中改了数据库的表结构,那么在不同的场景下会出现不同的问题:

一、场景1:vesion不变 - crash

此时运行app,crash欢迎你:

java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you’ve changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
复制代码

假设我们此时增加了版本号:

@Database(entities = {User.class}, version = 2)
public abstract class UsersDatabase extends RoomDatabase
复制代码

我们会遇到另一个问题:

二、场景2:增加vesion,但不提供Migration - crash

当我们把version从1改到了2。此时兴高采烈的从老版本升级上来后...crash欢迎你:

java.lang.IllegalStateException: A migration from 1 to 2 is necessary. Please provide a Migration in the builder or call fallbackToDestructiveMigration in the builder in which case Room will re-create all of the tables.
复制代码

三、场景3:增加vesion,使用fallback migration - 数据被清除

不提供自定义Migration,又不想引发crash,那么可以试试这个:

database = Room.databaseBuilder(context.getApplicationContext(),
                UsersDatabase.class, "Sample.db")
        .fallbackToDestructiveMigration()
        .build();
复制代码

不过,用之前请好好理解它的意思:

Room启动时将检测version是否发生增加,如果有,那么将找到Migration去执行特定的操作。如果没有因为fallbackToDestructiveMigration()。将会删除数据库并重建...

此时的确不会crash,但所有数据丢失。

四、场景4:vesion增加,提供Migration - 数据正常

如果version发生变化,即使表结构没有变化仍然需要提供Migration,那么此时应该怎么做呢?

@Database(entities = {User.class}, version = 2)
public abstract class UsersDatabase extends RoomDatabase {
// …
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        // 因为没有变化,所以是一个空实现
    }
};
// …
database =  Room.databaseBuilder(context.getApplicationContext(),
        UsersDatabase.class, "Sample.db")
        .addMigrations(MIGRATION_1_2)
        .build();
复制代码

五、场景5:改表结构

如果我们此时又想升级数据,并且这次需要更改表结构:给user表加一个last_update的字段。那么此时我们改怎么办?

1、先增加version

@Database(entities = {User.class}, version = 3)
public abstract class UsersDatabase extends RoomDatabase
复制代码

2、提供Migration

static final Migration MIGRATION_2_3 = new Migration(2, 3) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE users "
                + " ADD COLUMN last_update INTEGER");
    }
};
复制代码

PS:很多老铁,看到这可能觉得头疼。还得写这么多sql。其实有一个取巧的方式,当在User.class增加完字段,编译后全局搜索3.json。你会发现sql语句已经被生成出来了。

3、修改Room.databaseBuilder

database = Room.databaseBuilder(context.getApplicationContext(),
        UsersDatabase.class, "Sample.db")
        .addMigrations(MIGRATION_1_2, MIGRATION_2_3)
        .build();
复制代码

大功告成,基本的升库的用法我们已经了解完了。但是这也只是基本的用法,我相信有追求的小伙伴,可能需要来点更“刺激”的才行~

六、复杂的Migration

延续我们上边的表结构,此时我们想改表:把刚才增加的Int型的last_update改成String。

我猜有经验的老司机,已经猜到需要怎么做了:

  • 创建一个新的临时表,
  • 将数据从users表复制到临时表,
  • 删了users表
  • 将临时表重命名为users

那么针对此次更新,我们需要提供这样的Migration:

static final Migration MIGRATION_3_4 = new Migration(3, 4) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        // 创建临时表
        database.execSQL(
                "CREATE TABLE users_new (userid TEXT, username TEXT, last_update INTEGER, PRIMARY KEY(userid))");
        // 拷贝数据
        database.execSQL(
                "INSERT INTO users_new (userid, username, last_update) SELECT userid, username, last_update FROM users");
        // 删除老的表
        database.execSQL("DROP TABLE users");
        // 改名
        database.execSQL("ALTER TABLE users_new RENAME TO users");
    }
};
复制代码

此时我们用version3版本的app升级到version4的app的确没有问题,很爽。不过我们多想一个问题:如果从version1版本的用户直接升到version4版本会怎么样?

**其实不会有问题!**Room会对这种用户一直执行1-2的Migration,2-3的Migration已经3-4的Migration。这很方便,不过如果你追求极致的性能和体验,你可以提供一个1-4的Migration,比如这样:

static final Migration MIGRATION_1_4 = new Migration(1, 4) { // 除了这一行,其他的和上边一样
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        // 创建临时表
        database.execSQL(
                "CREATE TABLE users_new (userid TEXT, username TEXT, last_update INTEGER, PRIMARY KEY(userid))");
        // 拷贝数据
        database.execSQL(
                "INSERT INTO users_new (userid, username, last_update) SELECT userid, username, last_update FROM users");
        // 删除老的表
        database.execSQL("DROP TABLE users");
        // 改名
        database.execSQL("ALTER TABLE users_new RENAME TO users");
    }
};
复制代码

然后把Migration加进来就ok了。

database = Room.databaseBuilder(context.getApplicationContext(),
        UsersDatabase.class, "Sample.db")
        .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_1_4)
        .build();
复制代码

尾声闲谈

首先来说Room的基本用法其实主流的ORM库并没有什么不一样。在api体验上个人感觉各有千秋吧,不过升级这一块,我觉得Room还是蛮方便的。

再聊聊官方极力“吹捧”的:对RxJava和LiveData的封装。怎么说能...个人最初的体验的确是很爽,但是一旦表结构变得复杂起来之后,由其是多表查询的时候。你会发现你的LiveData的回调会爆炸的...所以个人对这个功能持保留意见,有好用的地方也有尴尬的时候...

我自己对Room的感觉就是...还行...哈哈~

我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,以及我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~

个人公众号:咸鱼正翻身

关注下面的标签,发现更多相似文章
评论