App版本更新時對SQLite資料庫升級或者降級遇到的問題
SQLite是Android內建的一個很小的關係型資料庫。SQLiteOpenHelper是一個用來輔助管理資料庫建立和版本升級問題的抽象類。我們可以繼承這個抽象類,實現它的一些方法來對資料庫進行自定義操作。下面兩個方法必須重寫:
- public void onCreate(SQLiteDatabase db)
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
另外SQLiteOpenHelper子類在構造例項時必須指定當前資料庫的名稱(name)、版本號(version)。而這裡名稱就決定了資料庫儲存時的檔名稱,而這裡的版本號與App在AndroidMainfest.xml定義的versionCode沒有絕對關聯。也就是在App更新時如果資料庫的資料結構沒有發生變化那麼資料庫的版本號則不用增加。
onCreate:呼叫時機是使用者首次安裝應用後啟動,或是清除App資料庫檔案後啟動。這時可以在這個函式中完成初始的資料表的建立。
onUpgrade:呼叫時機是使用者在做應用更新,覆蓋安裝後啟動,如果新版本中資料庫版本號要比舊版本中的資料庫版本號高則會呼叫。這時可以在這個函式完成資料庫版本升級帶來的舊版本的相容問題,以及資料遷移問題。
還有一個一般情況下不需要重寫,但在應用出現逆向降級(如應用由版本號4降級安裝版本號為3的包)時必須重寫的方法onDowngrade,如果應用降級覆蓋安裝時沒有重寫該方法則會崩潰。
在資料庫版本升級時, 我們可能會遇到這樣一些情況:
- 需要擴充套件一個表的欄位
- 刪除掉原來表上某個冗餘的欄位
- 新建一個表
而處理上面這些問題都要在不損害舊資料庫歷史資料的前提下完成。這裡我們假設使用者手機上之前安裝的是資料庫版本為1的包,升級安裝的是資料庫版本號為2的包。這時我們要在資料庫版本為2的包在去處理這些升級邏輯。
首先是擴充套件一個表的欄位在onUpgrade中的實現為:
MyDBHelper.java1234567 |
@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { //舊資料庫版本為1,才為表pedant新增一個student_name欄位 |
SQLite對ALTER TABLE的支援是有限的,你可以在一個存在表上新增一個欄位到末尾,或者是改變表的名稱。但如果你想做更復雜的操作,比如刪除一個表已有的欄位,就要重新建立這個表並完成資料遷移,而不能使用DROP COLUMN這樣方便的命令了。詳見SQLite Frequently Questions
比如表pedant原來有三個欄位a、b、c,現在想刪除c欄位,那麼在onUpgrade中寫法如下:
MyDBHelper.java123456789101112131415161718 | @Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { //舊資料庫版本為1,刪除表pedant的c欄位 if(oldVersion < 2) { db.beginTransaction(); try { db.execSQL("CREATE TEMPORARY TABLE pe_backup (a, b);"); db.execSQL("INSERT INTO pe_backup SELECT a, b FROM pedant;"); db.execSQL("DROP TABLE pedant;"); db.execSQL("CREATE TABLE pedant(a text, b text);"); db.execSQL("INSERT INTO pedant SELECT a, b FROM pe_backup;"); db.execSQL("DROP TABLE pe_backup;"); db.setTransactionSuccessful(); } finally { db.endTransaction(); } }} |
這樣就既完成了對c欄位的刪除也保留了原來表上的資料。
最後一種情況最簡單直接執行CREATE語句就要可以了。
MyDBHelper.java1234567 | @Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { //舊資料庫版本為1,建立新表newtb if(oldVersion < 2) { db.execSQL("CREATE TABLE newtb(a text, b text);"); }} |
資料庫在做升級時我們能明確地知道當前我們要對各舊錶進行什麼樣的操作來相容新版本。但如果在資料庫降級時,情況就不一樣了,針對我們開發新版本2時, 我們不能明確地知道以後的新版本比如版本3、4的資料庫結構走向是怎樣的。比如以後使用者從版本3回退到我們正在開發的版本2,由於我們開發當時不能預知版本3的表結構,不知版本3的資料表能否相容到版本2(假如版本3升級時刪除了一個版本2一直在用的表字段,這時回退資料結構可能就不相容了),那麼我們在開發版本2時最穩妥的做法是重寫onDowngrade時把所有當前版本將用到的表全部重建,即降級時扔掉以前全部的資料。
MyDBHelper.java12345678910 | // 因為我們無法預知未來版本的表結構,向下相容時最穩妥的方法就是將該版本自己需要的表重構一次@Overridepublic void onDowngrade (SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS t1;"); db.execSQL("DROP TABLE IF EXISTS t2;"); db.execSQL("DROP TABLE IF EXISTS t3;"); db.execSQL("DROP TABLE IF EXISTS t4;"); .... onCreate(db); // 建表} |