在上一篇博客[《打造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)