打造android ORM框架opendroid(七)——数据库升级方案

在上一篇博客《打造android ORM框架opendroid(六)——级联查询》我们讲了OpenDroid最后一块功能查询的实现原理。今天我们将进行OpenDroid一个重头戏,也是本系列博客的最后一篇——数据库升级方案。

说道数据库升级,我可是很头疼的, 为什么呢? 因为以前的项目中,根本没有考虑数据库升级方案的问题,就直接drop table了,这样导致的结果就是“以前的数据都消失了”。额。。。 凭空消失确实不是很少的一件事,如果数据不重要还行,重要数据呢? 说消失就消失了? 用户升级了一下软件,结果数据全没了。。。 那是多吊丝的一件事。

OpenDroid则提供了一种数据库升级的方案,当然这种方案肯定不是完美的。 肯定还有更好的方案,如果你发现你有好的解决方案,请不吝赐教。

好,下面开始进入正题。首先说说我的方案的原理吧:其实很简单,就是在drop table之前将数据查询出来,并保存到集合中,在创建新表后,尝试去insert数据。原理的思路很简单,以至于我一直认为这种方案太烂了, 可我没有想到更好的结果方案,也就只能先这样了。

大家都知道,android的SQLiteOpenHelper类中提供了一个抽象方法onUpgrade()来让用户灵活的定制数据库升级方案, 最简单的方法就是我之前提到直接drop table。既然upgrade的权利掌握在我们手中,那我们何不借onUpgrade()大干一番呢?

先来看看代码:

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    System.out.println("upgrade database");
    upgrade(db);
}

在onUpgrade里除了一句打印,其实真正有用了就一句代码,当然也是调用了我们自定义的一个方法,那么我们就从upgrade()这个方法开始说起:

/**
 * 升级数据库
 * @param db 数据库链接
 */
private <T extends OpenDroid> void upgrade(SQLiteDatabase db) {
    try {
        XmlPullParser pullParser = Xml.newPullParser();
        InputStream inputStream = DroidApplication.sContext.getAssets().open("open_droid.xml");
        pullParser.setInput(inputStream, "utf-8");  

        int type = pullParser.getEventType();
        while(type != XmlPullParser.END_DOCUMENT) {
            if(type == XmlPullParser.START_TAG) {
                // 获取mapping
                if(pullParser.getName().equals(OpenDroidHelper.TAG_MAPPING)) {
                    dumpData(db, pullParser);
                }
            }
            type = pullParser.next();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }  

    // 执行创建数据库
    onCreate(db);
}

7~9行可以看出我们准备去解析open_droid.xml文件了,和我们平时解析一样,使用一个while循环,观察while循环,我们在15~17行获取到了有用的信息,如果当前的tag是mapping的或,我们又去调用了dumpData,这里面XmlPullParser会作为第二个参数传递过去。

方法的最后,我们直接调用了重载的onCreate方法去创建新表,当然还有数据的恢复。这个我们稍后分析,接下来我们来看看dumpData方法。

/**
 * 将数据库中的数据转储到程序中
 * @param db 数据库连接
 * @param pullParser
 * @throws Exception
 */
private <T extends OpenDroid> void dumpData(SQLiteDatabase db, XmlPullParser pullParser)
        throws Exception {
    Class<OpenDroid> klass = (Class<OpenDroid>) Class.forName(pullParser.getAttributeValue(null, "class"));
    String tableName = klass.getSimpleName(); // 表名
    Cursor cursor = db.rawQuery("select * from " + tableName, null);  

    T t;
    Method m;
    String methodName;
    String columnName;  

    // 遍历游标
    for(cursor.moveToFirst();!cursor.isAfterLast();cursor.moveToNext()) {
        t = (T) klass.newInstance();  // 通过反射进行实例化
        final int columnCount = cursor.getColumnCount();
        for(int i=0;i<columnCount;i++) {
            columnName = cursor.getColumnName(i); // 获取字段名
            // try一下,如果没有该字段对应的方法,则消化异常,并继续
            try {
                switch (cursor.getType(i)) {
                case Cursor.FIELD_TYPE_INTEGER:
                    methodName = columnName.equals("_id") ? "setId" :
                        CRUD.getMethodName(cursor.getColumnName(i));
                    m = klass.getMethod(methodName, int.class); // 反射出方法
                    m.invoke(t, cursor.getInt(i)); // 执行方法
                    break;
                case Cursor.FIELD_TYPE_FLOAT:
                    methodName = CRUD.getMethodName(cursor.getColumnName(i));
                    m = klass.getMethod(methodName, float.class);
                    m.invoke(t, cursor.getFloat(i));
                    break;
                default:
                    methodName = CRUD.getMethodName(cursor.getColumnName(i));
                    m = klass.getMethod(methodName, String.class);
                    m.invoke(t, cursor.getString(i));
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        mOldData.add(t);
    }
    cursor.close();
    db.execSQL("drop table if exists " + tableName); // 删除旧的数据库
}

这个方法很长,而且也很关键,我们的数据库升级方案将在这里终结。

第9行,我们通过Class.forName获取了一个Class, 是根据什么映射呢?来看一下我们open_droid.xml文件就一目了然。

<open-droid>
    <version value="6" />
    <name value="school" />
    <mapping class="org.loader.opendroid.Student" />
    <mapping class="org.loader.opendroid.Grade" />
</open-droid>

这这个xml中,我们就是要通过org.loader.opendroid.Student来映射出一个类。

第10行,我们获取了该类的类名,当然也就是我们要操作的表名了,唉? 为什么就一个表呢? 仔细看看这个方法是在哪调用的,我们是在一个循环中调用了,也就是在循环中去遍历xml节点,每次获取到mapping节点,都来调用一下这个方法。

11行,我们执行一段select语句,将现在表中所有的数据查询出来,那查询出来的数据我们怎么处理呢?

要回答这个问题,我们就得去下面的for循环中找答案。

在for循环中,20行,通过反射实例化了上面那个类,为什么要在循环中实例化呢?因为每行数据我们都需要用一个对象来保存。

21行,获取了当前行所有列的个数。

接下来有一个for循环,这个循环我们是循环的每一行的列,在循环中去取每一列的数据。

26行,进入一个switch语句,依照惯例,我们只去分析一个case语句。

在第一个case中,如果改列的字段是一个integer类型,28行,我们和之前讲过的一样去拼装一个setter,当然如果是_id的话,我们就直接定义为setId了。

30行,反射出这个方法,等待下面去执行。

当然31行我们就要去执行这个方法了,我们都知道setter方法是需要参数的,参数当然就是我们查询出来的当前列的数据了。

48行,我们将这个对象的实力放入一个集合中。

当查询完当前表,这个表就没用了,因为我们已经把数据都保存起来了,所以在51行,将该表删除。

至此,我们就把数据从旧版本的数据库中全部查询出来了。接下来我们回到onCreate方法中来看看新表是如果创建的,并且数据是如何恢复的。

@Override
public void onCreate(SQLiteDatabase db) {
    for(String sql : OpenDroidHelper.getDBInfoBean().getSqls()) {
        db.execSQL(sql);
    }  

    // 还原数据
    if(!mOldData.isEmpty()) {
        for(OpenDroid item : mOldData) {
            item.save(db);
        }
    }
}

前面几行代码,我们在《打造android ORM框架opendroid(二)——自动创建数据库》 已经讲解过,这里就不重复了,我们重点来看看在那篇博客中省略的几行代码,也正是这几行代码,实现了旧数据向新表中的转移。

8行,先去判断mOldData是否为空的集合,因为onCreate方法并不是只有在数据库升级的时候才去执行。

接下来遍历整个集合,并且调用每个item的save方法将数据保存到新表中,当然这里我们重用了OpenDroid类中的save方法,因为都是insert嘛。从这里我们也可以看出这个mOldData集合的泛型肯定是OpenDroid。

private ArrayList<OpenDroid> mOldData = new ArrayList<OpenDroid>();

好了,至此,我们opendroid提供的一个简单的数据库升级方案就执行完了,而且我们的opendroid也介绍的差不多了,剩下的一点东西都是辅助性的东西。哦,对了,这里还要提一点:细心的朋友可能已经发现了,opendroid在操作完数据库并没有默认的关闭掉数据库,而是蛋疼的提供了open和release两个方法,不信可以看代码:

/**
 * 打开数据库
 */
public static void open() {
    if(sSqliteDatabase == null) {
        sSqliteDatabase = sOpenHelper.getWritableDatabase();
    }
}  

/**
 * 释放数据库
 */
public static void release() {
    if(sSqliteDatabase != null && sSqliteDatabase.isOpen()) {
        sSqliteDatabase.close();
    }
    sSqliteDatabase = null;
}

这是为什么呢? 其实刚开始我是做了默认关闭了,可就是在写到数据库升级恢复数据的时候,因为save是在一个循环中执行了,因此,可能在很短的时间内多次开启/关闭数据库,这样做会消耗很大的性能,所以android会抛出一个异常,在一气之下,我就将代码改造成了这种方式,如果有大神有更好的解决方案,请赐教哈。

好了,至此我们opendroid系列博客也就尾声了,当然,做出一个orm框架本身并不重要,重要的是学会如何去做一个orm框架,别人能做的事,我们为什么就不能呢?对吧,作为一个程序员,我们要努力去做一个“创造者”,而不是简单停留在一个“使用者”上。

最后是opendroid的开源地址:http://git.oschina.net/qibin/OpenDroid

时间: 2024-08-06 03:41:04

打造android ORM框架opendroid(七)——数据库升级方案的相关文章

打造android ORM框架opendroid(二)——自动创建数据库

在上一篇博客<打造android ORM框架opendroid(一)--ORM框架的使用>中相信你已经了解了opendroid的使用,那么从这篇博客开始,我们正式进入opendroid的源码分析,打造一款自己的ORM框架! 在正式开始之前,你需要保证手里有一份opendroid的源码,如果还没下载opendroid,请到http://git.oschina.net/qibin/OpenDroid 下载opendroid的源码. 任何数据库操作都是从创建数据库开始的,今天我们就来看看opendr

打造android ORM框架opendroid(五)——数据更新的实现

在上一篇博客<打造android ORM框架opendroid(四)--优雅的删除数据>中,我们介绍了opendroid是如何优雅的从数据库中删除数据的,也可以看到opendroid的设计是如此的简单,其实opendroid只是我作为兴趣或者说是抱着试试的态度写的,当然它肯定存在诸多不足,但是这并不影响我们去了解一个orm框架的流程. 废话不说了,下面进入主题,今天我选择去了解的是opendroid的update流程,其实,对于已经了解了delete操作的朋友们来说,今天的update流程肯定

打造android ORM框架opendroid(六)——级联查询

在上一篇博客<打造android ORM框架opendroid(五)--数据更新的实现>  我们介绍了opendroid数据更新的流程,也就在上次,我们OpenDroid类中的所有操作都介绍完了, 那查询操作呢?不是在OpenDroid中?查询操作是在OpenDroid中,不过是以内部类的形式呈现的. 还是来看看如果使用opendroid查询数据吧. OpenDroid.query.find(Student.class) OpenDroid.query.columns("stuNam

打造android ORM框架opendroid(四)——优雅的删除数据

在上一篇博客<打造android ORM框架opendroid(三)--持久化数据>中,我们感受到了opendroid保存数据的流程,今天的博客我们来顺一下opendroid是如何删除数据的. 还记得我们在第一篇博客<打造android ORM框架opendroid(一)--ORM框架的使用>中介绍过opendroid的使用,先来回顾一下怎么利用opendroid来删除数据吧. int length = OpenDroid.delete(Student.class, 1, 2, 3

打造android ORM框架opendroid(三)——持久化数据

在上一篇博客<打造android ORM框架opendroid(二)--自动创建数据库>中,我们介绍了opendroid是怎么做到自动帮我们创建好数据库并通过反射拼凑出创建数据库的SQL语句,接着上面的博客,今天要来介绍一下opendroid数据库持久化(也就是insert操作)是怎么一个流程. 废话不多少,我们马上进入主题. ... 还记得通过opendroid我们是如何将数据保存到数据库的吗? 当时是调用了从OpenDroid类继承过来的save方法,来回顾一下吧. Student stu

打造android ORM框架opendroid(一)——ORM框架的使用

一.我的看法 我记得曾经有一篇博客是介绍的litepal的使用,在这篇博客中我提到过:本来以为android本身提供的API已经封装的够好了,根本不需要什么ORM框架了,但是在使用了litepal后,我感觉使用ORM框架还是很有必要的,下面是我对ORM和android API的几点看法: 1.做为API级别, android只能广义封装,而不能特定去封装一个API,所以android 对sqlite的封装已经很强大了. 2.作为开发者,我们需要为项目提供足够适配的解决方案,可能ORM框架比API

Android Orm框架(GreenDao)

Android Orm框架(GreenDao) 分类: android2014-04-10 14:29 723人阅读 评论(0) 收藏 举报 GreenDao与Ormlite对比 Ormlite:简单好用,比较符合JavaEE开发者使用习惯,注解很方便: GreenDao:为Android大大优化 ,最小的内存使用 ,非常高的性能优势. 官网地址:http://greendao-orm.com/features/ 项目地址:https://github.com/greenrobot/greenD

一劳永逸的数据库升级方案

数据库升级方案 一.面临的问题 在项目中数据库升级是经常遇到的事情,这个工作比较繁琐,特别是在线数据库升级需要十分小心,我们先来看一下通常面临的问题: 1.    表修改,包括增加了字段.修改了字段类型或者长度,更换了主键等.对于表的升级不能删除重建,需要单独修改,或者写脚本来升级. 2.    视图的修改,视图的修改比较简单,无非是增加了字段,取消了字段,不影响基础数据.视图的升级可以删除重建. 3.    存储过程的修改,存储过程的修改和视图一样,可以删除重建,二者都可以通过脚本来完成. 4

Android ORM 框架之 ActiveAndroid应用基础

ActiveAndroid作为轻量级的ORM框架,在快速开发中,使用很简单,满足大部分对数据库操作不复杂的应用. 一,配置 添加依赖 build.gradle中添加: repositories { mavenCentral() maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } } compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'