GreenDao 兼容升級,保留舊數據的---全方面解決方案

做者:林冠宏 / 指尖下的幽靈java

掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8android

博客:http://www.cnblogs.com/linguanh/git

GitHub : https://github.com/af913337456/github

騰訊雲專欄: https://cloud.tencent.com/developer/user/1148436/activitiessql

開源地址:github.com/af913337456…數據庫


本文不會花時間去談 greenDao 是什麼

使用 greenDao 做爲線上APP 的本地 orm 框架時候,總有數據庫表要更新的一天,或早或遲。框架

目錄

  • 出問題的的情形

  • 幾個事實

  • 解決方案

  • 代碼簡述

  • 產品級別的可能錯誤

  • 你的顧慮

出問題的的情形:

  • 字段添加,致使舊錶格字段與新的不匹配引起 android.database.sqlite.SQLiteException 類異常。
  • 服務端數據返回沒法與就表格匹配,沒法進行插入操做

第一個狀況會直接致使 APP 閃退掉,第二種就是數據不匹配。ide

幾個事實

  • GreenDao 目前的 3.+ 版,自動生成的代碼的升級方式都是先刪除原來的表格,再建立新的
/** WARNING: Drops all table on Upgrade! Use only during development. */
public static class DevOpenHelper extends OpenHelper {
    ......
    @Override
    public void onUpgrade(Database db, int oldVersion, int newVersion) {
        Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
        dropAllTables(db, true);  // 刪除-----①
        onCreate(db);
    }
}
複製代碼
  • 凡是自動生成的代碼文件,例如 xxxDao.java 類的,都會在每一次 build 的時候從新被生成,意味着我的的內嵌修改老是無效,由於老是覆蓋你的。
  • 數據庫的升級方式需求更可能是須要日後兼容的,舊數據不能丟失

解決方案

自定義升級策略。 思路參考ui

在上面的基礎上作出以下步驟總結: (看不懂的看下面的符號描述)this

  • 建立以前舊錶中不存在的新表
  • 建立中間表 & 把舊錶的數據遷移到中間表
  • 把舊錶所有刪除
  • 建立全部新表
  • 把中間表的數據遷移到新表 & 刪除中間表

對應上面的步驟描述:

  • A -> A + B , old: A , new: B
  • use (A+B) -> create temp (A'+B') & insert data
  • drop (A+B) , contain old datas
  • create (A+B) , abs empty tables
  • restore data to (A+B) from (A'+B') then drop (A'+B')

代碼簡述

基於上面的二次修改和拓展

  • GreenDaoCompatibleUpdateHelper.java 顧名思義,兼容舊錶性質的 greenDao 數據庫升級,不會形成舊錶的數據丟失

    • 拓展了最終的成功和失敗的回調
    • 添加了錯誤日誌的處理
    • 解決了字段名稱的衝突 bug,例如 delete 之類
  • MyGreenDaoDbHelper.java 自定義的 dbHelper,重載 onUpgrade

調用例子

if (oldVersion < newVersion) {
    Log.e("MyGreenDaoDbHelper","進行數據庫升級");
    new GreenDaoCompatibleUpdateHelper()
            .setCallBack(
                    new GreenDaoCompatibleUpdateHelper.GreenDaoCompatibleUpdateCallBack() {
                        @Override
                        public void onFinalSuccess() {
                            Log.e("MyGreenDaoDbHelper","進行數據庫升級 ===> 成功");
                        }

                        @Override
                        public void onFailedLog(String errorMsg) {
                            Log.e("MyGreenDaoDbHelper","升級失敗日誌 ===> "+errorMsg);
                        }
                    }
            )
            .compatibleUpdate(
                    db,
                    PostBeanDao.class,
                    MatterUserBeanDao.class,
                    PropsBeanDao.class,
                    ChannelChatsBeanDao.class,
                    JoinToChannelReqBeanDao.class
            );
    Log.e("MyGreenDaoDbHelper","進行數據庫升級--完成");
}
複製代碼
GreenDaoCompatibleUpdateHelper
public final class GreenDaoCompatibleUpdateHelper {

    public interface GreenDaoCompatibleUpdateCallBack{
        void onFinalSuccess();
        void onFailedLog(String errorMsg);
    }

    private static GreenDaoCompatibleUpdateCallBack callBack;

    @SuppressWarnings("all")
    public void compatibleUpdate(SQLiteDatabase sqliteDatabase, Class<? extends AbstractDao<?, ?>>... daoClasses) {
        StandardDatabase db = new StandardDatabase(sqliteDatabase);
        /** 建立以前舊錶中不存在的新表 */
        if(!generateNewTablesIfNotExists_withNoExchangeData(db, daoClasses))    
            return;
        /** 建立中間表 & 把舊錶的數據遷移到中間表 */
        if(!generateTempTables_withExchangeDataFromOldTable(db, daoClasses))   
            return;
        /** 把舊錶所有刪除 */
        if(!dropAllTables(db, true, daoClasses))                         
            return;
        /** 建立全部新表 */
        if(!createAllTables_withNoExchangeData(db, false, daoClasses)) 
            return;
        /** 把中間表的數據遷移到新表 & 刪除中間表 */
        restoreData_fromTempTableToNewTable(db, daoClasses);                     
        if(callBack != null)
            callBack.onFinalSuccess();
        callBack = null;
    }

    public GreenDaoCompatibleUpdateHelper setCallBack(GreenDaoCompatibleUpdateCallBack callBack1){
        callBack = callBack1;
        return this;
    }
    ...... // 去 gitHub 下載完整代碼
}
複製代碼
MyGreenDaoDbHelper
public class MyGreenDaoDbHelper extends DaoMaster.DevOpenHelper {

    public MyGreenDaoDbHelper(Context context, String name) {
        super(context, name);
    }

    public MyGreenDaoDbHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
        super(context, name, factory);
    }

    @Override
    @SuppressWarnings("all")
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        super.onUpgrade(db, oldVersion, newVersion);
        Log.e("MyGreenDaoDbHelper", "----"+oldVersion + "---先前和更新以後的版本---" + newVersion+"----");
        if (oldVersion < newVersion) {
            Log.e("MyGreenDaoDbHelper","進行數據庫升級");
            new GreenDaoCompatibleUpdateHelper()
                    .setCallBack(
                            new GreenDaoCompatibleUpdateHelper.GreenDaoCompatibleUpdateCallBack() {
                                @Override
                                public void onFinalSuccess() {
                                    Log.e("MyGreenDaoDbHelper","進行數據庫升級 ===> 成功");
                                }

                                @Override
                                public void onFailedLog(String errorMsg) {
                                    Log.e("MyGreenDaoDbHelper","升級失敗日誌 ===> "+errorMsg);
                                }
                            }
                    )
                    .compatibleUpdate(
                            db,
                            PostBeanDao.class,
                            MatterUserBeanDao.class,
                            PropsBeanDao.class,
                            ChannelChatsBeanDao.class,
                            JoinToChannelReqBeanDao.class
                    );
            Log.e("MyGreenDaoDbHelper","進行數據庫升級--完成");
        }
    }

    @Override
    public void onUpgrade(Database db, int oldVersion, int newVersion) {
        // 不要調用父類的,它默認是先刪除所有表再建立
        // super.onUpgrade(db, oldVersion, newVersion);

    }
}
複製代碼

產品級別的可能錯誤

  • 由於混淆了 dao 類文件,致使 createTable 方法找不到,解決方法,不要混淆 dao 文件
  • restore 步驟中由於新加入的字段含有 int boolean 基礎類型,由於不具有默認值而致使出現 SQLiteConstraintException: NOT NULL constraint failed 錯誤,解決方法,採用 Integer Boolean 類型替換,這個你只能妥協,由於 greenDao 做者不屑於在你建表的時候提供默認值方法。詳情能夠去看看 issue

你的顧慮

  • 若是個人表太多了,升級會不會形成 ANR 或者致使讀寫混亂?
  • 是否實踐過?

1, 答: sqlLite 的源碼裏面調用 onUpdrade方法的入口皆加上了同步瑣,這樣不會形成在升級中還能讓你去讀寫的狀況。 這點設計得很是優秀!表太多的,幾百張?那麼就放入子線程升級。

2, 答: 我已經使用到線上多個APP , 且成功運行至今。

相關文章
相關標籤/搜索