Android jetpack Room數據庫(二)版本升級/遷移

前面演示兩位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,以上內容就是數據庫升級/遷移

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章