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

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

GreenDao 兼容升级,保留旧数据的---全方面解决方案的更多相关文章

  1. Android版本升级同时Sqlite数据库的升级及之前数据的保留

    http://www.cnblogs.com/wang340/archive/2013/05/06/3063135.html http://www.eoeandroid.com/forum.php?m ...

  2. 前端必备——js中前端与后台的数据交互全解

    只要编程语言能够支持网卡端口的监听和发送,理论上都是可以实现服务器后台设计的.也因此造成了实现后台的语言偏多,而web前端语言以html/css/js为主.所以在这里我们不涉及后台的设计,只介绍在we ...

  3. 从 vCenter Server 使用的数据库中清除旧数据 (2075138)(转)

    Document Id 2075138 Symptoms 免责声明: 本文为 Purging old data from the database used by VMware vCenter Ser ...

  4. solr6.4.2之webservice兼容升级

    摘要:这次solr底层升级是一次比较大的升级.从底层搜索引擎 solr4.8 升级到 solr6.4.2,由于solr底层从6.x开始以来的jdk必须指定为1.8,而且很多内部实现类都已经废弃或者干脆 ...

  5. 尽可能保留原有数据,建立UEFI与BIOS双启PE优盘

    尽可能保留原有数据,建立UEFI与BIOS双启PE优盘1.确保优盘或者移动硬盘有一个FAT32分区,如果没有FAT32分区,就用傲梅分区助手或者ppm转换一个现有的分区到FAT32分区0x0C,或者新 ...

  6. mysql更改已有数据表的字符集,保留原有数据内容

    mysql更改已有数据表的字符集,保留原有数据内容     原文网址:http://blog.csdn.net/learn_2/article/details/6460370 环境:在应用开始阶段没有 ...

  7. 资源下载南方cass视频教程,包括文档,数据,很全的

    废话就不多说了,开始... 北方cass视频教程,包括文档,数据,很全的 视频下载地址:http://www.400gb.com/file/23459263 GIS网盘进入下载:http://laoh ...

  8. 导入旧数据需要 使用date插件

    "@version" => "1", "@timestamp" => "2016-09-12T08:31:06.630 ...

  9. 【甘道夫】使用HIVE SQL实现推荐系统数据补全

    需求 在推荐系统场景中,假设基础行为数据太少,或者过于稀疏,通过推荐算法计算得出的推荐结果非常可能达不到要求的数量. 比方,希望针对每一个item或user推荐20个item,可是通过计算仅仅得到8个 ...

随机推荐

  1. 【批处理学习笔记】第十四课:常用DOS命令(4)

    系统管理at 安排在特定日期和时间运行命令和程序shutdown立即或定时关机或重启taskkill结束进程(WinXPHome版中无该命令)tasklist显示进程列表(Windows XP Hom ...

  2. (a == 1 && a == 2 && a == 3),何时为true?

    今天浏览一些技术网站,看到这个题目.虽然觉着代码这么写的可能性低之又低,但是却也考验对js了解的程度. 在 JavaScript 中 (a ==1 && a== 2 && ...

  3. Spring框架学习笔记(8)——AspectJ实现AOP

    使用代理对象实现AOP虽然可以满足需求,但是较为复杂,而Spring提供一种简单的实现AOP的方法AspectJ 同样的计算器的DEMO 首先配置applicationContext.xml < ...

  4. Kubernetes存储之Persistent Volumes简介

    简介 管理存储和管理计算有着明显的不同.PersistentVolume子系统给用户和管理员提供了一套API,从而抽象出存储是如何提供和消耗的细节.在这里,我们介绍两种新的API资源:Persiste ...

  5. 使用ng-options指令创建下拉框

    今天在学习AngularJs中使用ng-options指令创建下拉框时遇到点问题,这里总结一下. 其实,使用ng-options指令创建下拉框很简单,只需要绑定两个属性. ng-options指令用于 ...

  6. 算法-java代码实现选择排序

    选择排序  

  7. EntityFramework默认映射规则

    我不太习惯通过CodeFirst去维护数据库(尽管这是未来实现自动编程的必经之路),还是喜欢通过数据库设计工具如PowerDesigner去建表.如果不想对EF的实体和数据表做什么映射的话,就要注意默 ...

  8. 怎么解决dede首页网址自动加上index.html

    怎样去掉dedecms5.7(织梦)首页url后index.html有三种方法 1.去配置你的空间的默认首页地址.把index.html移到默认文本最前面.(确保你的默认文档里面有index.html ...

  9. JavaSE笔记-泛型

    定义带泛型的类 public class Cat<T> { //可以用T定义实例变量 private T name; //可以用T定义形参 //构造器没有<> public C ...

  10. asp.net -mvc框架复习(3)-控制器和动作方法的任务分析

    using System;using System.Collections.Generic;using System.Linq;using System.Web;//ASP.NET核心命名空间usin ...