前面演示两位room数据库的基本使用,今天来看一下数据库的升级/迁移。本文将以新增表和新增列为例来讲解。

这里用到一个数据库调试工具Stetho,大家可以去看看用法:https://github.com/facebook/stetho

1.新加一个数据表

1.1.这样定义未指定主键不能为null,会报错如下:

@Entity(tableName = "device",primaryKeys = {"id"})
public class Device {
 
    private String id;
    private String location;
    private String deviceName;
    private String deviceType;
    
    ...
    
}
错误: You must annotate primary keys with @NonNull. "id" is nullable. SQLite considers this a bug and Room does not allow it. See SQLite docs for details: https://www.sqlite.org/lang_createtable.html
​

 

所以需要指定主键以及主键不能为null,添加注解@@NonNull

@Entity(tableName = "device",primaryKeys = {"id"})
public class Device {
    @NonNull //主键不允许为null值
    private String id;
    private String location;
    private String deviceName;
    private String deviceType;
    
    ...
    
}

 

1.2.如果忘记在database的entities中加入我们刚才新建的类,会报如下错误:

错误: There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (no such table: device)
找不到这个表

 

1.3.如果忘记更新版本号了会报错:

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.
译:room无法验证数据库完整性,看起来是你改变了架构但是忘记更新数据库版本了。你只需要增加版本号就可以解决这个问题

1.4.你以为你增加了版本号就解决了

当然,编译时通过了,不过运行的时候就会发现出现了如下问题:

    java.lang.IllegalStateException: A migration from 1 to 2 was required but not found. Please provide the necessary Migration path via RoomDatabase.Builder.addMigration(Migration ...) or allow for destructive migrations via one of the RoomDatabase.Builder.fallbackToDestructiveMigration* methods.
译:没有找到数据库从版本号1迁移到版本号2的迁移文件。请通过RoomDatabase.Builder.addMigration()方法增加迁移文件,或者RoomDatabase.Builder.fallbackToDestructiveMigration()方法进行处理。

1.5.OK,既然有处理方案,那么先捡简单的来

在创建数database实例的时候,就是通过RoomDatabase.Builder.build()来完成的,我们就在build之前增加这个fallbackToDestructiveMigration()方法试试。先看看系统对fallbackToDestructiveMigration()方法的介绍

/**
         * Allows Room to destructively recreate database tables if {@link Migration}s that would
         * migrate old database schemas to the latest schema version are not found.
         如果找不到旧版本数据库迁移到新版本数据库的迁移文件,并且设置了此方法,那么数据库将会删除重建
         
         * <p>
         * When the database version on the device does not match the latest schema version, Room
         * runs necessary {@link Migration}s on the database.
         * <p>
         * If it cannot find the set of {@link Migration}s that will bring the database to the
         * current version, it will throw an {@link IllegalStateException}.
         * <p>
         * You can call this method to change this behavior to re-create the database instead of
         * crashing.
         * <p>
         
         一般情况下,当设备数据库版本号与最新架构的版本号不一致时,room将在数据库上运行必要的Migration文件,如果找不到对应的Migration将会抛出异常IllegalStateException,你可以调用此方法来重新创建数据库而不是抛出异常。
         
         * If the database was create from an asset or a file then Room will try to use the same
         * file to re-create the database, otherwise this will delete all of the data in the
         * database tables managed by Room.
         
         如果数据库是使用asset或者file创建的,则room将尝试使用相同的文件创建数据库。否则将删除所有的数据表
         * <p>
         * To let Room fallback to destructive migration only during a schema downgrade then use
         * {@link #fallbackToDestructiveMigrationOnDowngrade()}.
         *
         * @return This {@link Builder} instance.
         *
         * @see #fallbackToDestructiveMigrationOnDowngrade()
         */
        @NonNull
        public Builder<T> fallbackToDestructiveMigration() {
            mRequireMigration = false;
            mAllowDestructiveMigrationOnDowngrade = true;
            return this;
        }

2.利用fallbackToDestructiveMigration()强制升级:

看一下使用了fallbackToDestructiveMigration()方法后的效果,代码:


    public static AppDataBase getInstance(Context context) {
        if (instance == null) {
            synchronized (AppDataBase.class) {
                if (instance == null) {
                    instance = Room.databaseBuilder(context.getApplicationContext(),    AppDataBase.class, DATABASE_NAME)
                            .fallbackToDestructiveMigration()//数据库更新时删除数据重新创建
                            .build();
                }
            }
        }
        return instance;
    }

效果如下,创建成功:

 

ok,这是最简单的数据库更新,接下来我们来看一下通过Migration来更新数据库

3.使用Migration升级策略进行升级

3.1.首先,先定义我们版本升级的策略,如我们现在是需要增加一个表device

/**
     * 版本1-2的迁移策略
     * 构造方法需传 开始版本号 与 截止版本号
     */
    static final Migration MIGRATION_1_2 = new Migration(1,2) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            //将数据表device创建出来
            database.execSQL("CREATE TABLE 'device' ('id'  TEXT NOT NULL,'location' TEXT,'deviceName' TEXT,'deviceType' TEXT,PRIMARY KEY ('id')) ");
        }
    };

3.2.然后,在数据库初始化的时候加入版本1-2的迁移策略,这次我们不使用fallbackToDestructiveMigration方法

  
public static AppDataBase getInstance(Context context) {
        if (instance == null) {
            synchronized (AppDataBase.class) {
                if (instance == null) {
                    instance = Room.databaseBuilder(context.getApplicationContext(), AppDataBase.class, DATABASE_NAME)
//                            .fallbackToDestructiveMigration()//数据库更新时删除数据重新创建
                            .addMigrations(MIGRATION_1_2)//指定版本1-2升级时的升级策略
                            .build();
                }
            }
        }
        return instance;
    }

3.3.最后看效果:

 

4.增加数据表的列的数据库升级

4.1.接下来,我们试一下给device表增加一列,不需要默认值,

@Entity(tableName = "device",primaryKeys = {"id"})
public class Device {
    @NonNull //主键不允许为null值
    private String id;
    private String location;
    private String deviceName;
    private String deviceType;
    //新增一列
    private String deviceCode;
    ...
    }
​

如果不生成对应的get和set方法,会报错:

错误: Cannot find getter for field.解决这个问题有两种方法

a.不需要在数据表中生成此列,可以直接加上@Ignore注解,(这样操作的不用后续操作了)

b.生成对应的get与set方法(即需要变更数据库,需要继续操作)

注意:记得更新数据库版本号和新增数据库迁移策略,否则将会重现3、4步骤周出现的问题

迁移策略

 /**
     * 版本2-3的迁移策略
     * 构造方法需传 开始版本号2 与 截止版本号3
     */
    static final Migration MIGRATION_2_3 = new Migration(2,3) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            //为device表增加一列
            database.execSQL("ALTER TABLE device ADD COLUMN deviceCode TEXT ");
        }
    };
    。。。
        public static AppDataBase getInstance(Context context) {
        if (instance == null) {
            synchronized (AppDataBase.class) {
                if (instance == null) {
                    instance = Room.databaseBuilder(context.getApplicationContext(), AppDataBase.class, DATABASE_NAME)
//                            .fallbackToDestructiveMigration()//数据库更新时删除数据重新创建
                            .addMigrations(MIGRATION_1_2,MIGRATION_2_3)//指定版本升级时的升级策略
                            .build();
                }
            }
        }
        return instance;
    }

然后测试如下图,新的列已经添加成功:

注意:版本升级时需要下次操作数据库时才会生效的!!!

 

4.2.接下来我们再给device表增加一列,并且加默认值:

同样的,在实体类里面增加一个字段—>添加版本迁移策略—>更新database版本号,运行代码

/**
     * 版本3-4的迁移策略
     * 构造方法需传 开始版本号3 与 截止版本号4
     */
    static final Migration MIGRATION_3_4 = new Migration(3,4) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            //为device表增加一列
            database.execSQL("ALTER TABLE device ADD COLUMN managerType TEXT NOT NULL DEFAULT 'A类' ");
        }
    };
    
    。。。
    
     public static AppDataBase getInstance(Context context) {
        if (instance == null) {
            synchronized (AppDataBase.class) {
                if (instance == null) {
                    instance = Room.databaseBuilder(context.getApplicationContext(), AppDataBase.class, DATABASE_NAME)
//                            .fallbackToDestructiveMigration()//数据库更新时删除数据重新创建
                            .addMigrations(MIGRATION_1_2,MIGRATION_2_3,MIGRATION_3_4)//指定版本升级时的升级策略
                            .build();
                }
            }
        }
        return instance;
    }
    

OK,原有的数据也加上了默认值了

 

ok,以上内容就是数据库升级/迁移

 

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐