GreenDao 兼容升级,保留旧数据的---全方面解决方案

作者:林冠宏 / 指尖下的幽灵

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

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

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

腾讯云专栏: https://cloud.tencent.com/developer/user/1148436/activities

GreenDaoCompatibleUpdateHelper

一个能帮助你成功升级基于 greenDao 创建的数据库而不会丢失之前数据的工具 A helper which can help you to update you database without lost your old datas, simple to use , enjoy.

开源地址

https://github.com/af913337456/LghGreenDaoCompatibleUpdateHelper


目录

  • 出问题的的情形
  • 几个事实
  • 解决方案
  • 代码简述
  • 产品级别的可能错误
  • 你的顾虑

出问题的的情形:

  • 字段添加,导致旧表格字段与新的不匹配引发 android.database.sqlite.SQLiteException 类异常。
  • 服务端数据返回无法与就表格匹配,无法进行插入操作

第一个情况会直接导致 APP 闪退掉,第二种就是数据不匹配。

几个事实

  • 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 的时候重新被生成,意味着个人的内嵌修改总是无效,因为总是覆盖你的。
  • 数据库的升级方式需求更多是需要往后兼容的,旧数据不能丢失

解决方案

自定义升级策略。 思路参考

在上面的基础上做出如下步骤总结: (看不懂的看下面的符号描述)

  • 创建之前旧表中不存在的新表
  • 创建中间表 & 把旧表的数据迁移到中间表
  • 把旧表全部删除
  • 创建所有新表
  • 把中间表的数据迁移到新表 & 删除中间表

对应上面的步骤描述:

  • 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 , 且成功运行至今。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏MasiMaro 的技术博文

WinSock Socket 池

之前在WinSock2.0 API 中说到,像DisConnectEx 函数这样,它具有回收SOCKET的功能,而像AcceptEx这样的函数,它不会自己在内部...

1501
来自专栏阿杜的世界

Spring的容器内部事件发布自定义事件机制Spring 的容器内事件发布类结构应用场景

EventListener接口的作用仅仅在于“标记”,具体要提供哪些功能需要开发者自己定义,而且,还需要为自己定义的接口提供一个默认的实现类——只有接口的话什么...

1022
来自专栏北京马哥教育

Linux 中 fcntl()、lockf、flock 的区别

这三个函数的作用都是给文件加锁,那它们有什么区别呢?首先flock和fcntl是系统调用,而lockf是库函数。lockf实际上是fcntl的封装,所以lock...

59411
来自专栏比原链

剥开比原看代码07:比原节点收到“请求区块数据”的信息后如何应答?

Gitee地址:https://gitee.com/BytomBlockchain/bytom

892
来自专栏蓝天

Thrift线程和状态机分析

启动Thrift时,可启动两类线程,一是TNonblockingIOThread,另一是Worker:

1641
来自专栏阿杜的世界

分布式系统学习资料(ing)一、理论知识二、数据库三、分布式服务调用四、消息系统

1052
来自专栏有趣的django

python爬虫人门(10)Scrapy框架之Downloader Middlewares

设置下载中间件(Downloader Middlewares)  下载中间件是处于引擎(crawler.engine)和下载器(crawler.engine.d...

3768
来自专栏非典型程序猿

使用与源码剖析(二)

1.5K8
来自专栏以南小隐-数通那些事儿

锐捷设备主程序下升级方法

1754
来自专栏为数不多的Android技巧

Android插件化原理解析——广播的管理

在Activity生命周期管理 以及 插件加载机制 中我们详细讲述了插件化过程中对于Activity组件的处理方式,为了实现Activity的插件化我们付出了相...

1062

扫码关注云+社区

领取腾讯云代金券