企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
在上一篇博客[《打造android ORM框架opendroid(六)——级联查询》](http://blog.csdn.net/qibin0506/article/details/43370135)我们讲了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 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 extends OpenDroid> void dumpData(SQLiteDatabase db, XmlPullParser pullParser)             throws Exception {         Class klass = (Class) 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             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(二)——自动创建数据库》](http://blog.csdn.net/qibin0506/article/details/42773281) 已经讲解过,这里就不重复了,我们重点来看看在那篇博客中省略的几行代码,也正是这几行代码,实现了旧数据向新表中的转移。 8行,先去判断mOldData是否为空的集合,因为onCreate方法并不是只有在数据库升级的时候才去执行。 接下来遍历整个集合,并且调用每个item的save方法将数据保存到新表中,当然这里我们重用了OpenDroid类中的save方法,因为都是insert嘛。从这里我们也可以看出这个mOldData集合的泛型肯定是OpenDroid。 `private ArrayList mOldData = new ArrayList();  ` 好了,至此,我们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](http://git.oschina.net/qibin/OpenDroid)