打造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

你可能感兴趣的:(框架,orm,opendroid)