ITKeyword,专注技术干货聚合推荐

注册 | 登录

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

qibin0506 分享于 2015-02-09

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

在上一篇博客《打造android ORM框架opendroid(二)——自动创建数据库》中,我们介绍了opendroid是怎么做到自动帮我们创建好数据库并通过反射拼凑出创建数据库的S

2020腾讯云共同战“疫”,助力复工(优惠前所未有!4核8G,5M带宽 1684元/3年),
地址https://cloud.tencent.com/act/cps/redirect?redirect=1054

2020阿里云最低价产品入口,含代金券(新老用户有优惠),
地址https://www.aliyun.com/minisite/goods

在上一篇博客 《打造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方法中来看看新表是如果创建的,并且数据是如何恢复的。

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

在上一篇博客《打造android ORM框架opendroid(五)——数据更新的实现》  我们介绍了opendroid数据更新的流程,也就在上次,我们OpenDroid类中的所有操作都介绍完


@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

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

在上一篇博客《打造android ORM框架opendroid(四)——优雅的删除数据》中,我们介绍了opendroid是如何优雅的从数据库中删除数据的,也可以看到opendroid的设计是

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

相关阅读排行


相关内容推荐

最新文章

×

×

请激活账号

为了能正常使用评论、编辑功能及以后陆续为用户提供的其他产品,请激活账号。

您的注册邮箱: 修改

重新发送激活邮件 进入我的邮箱

如果您没有收到激活邮件,请注意检查垃圾箱。