在上一篇文章中,我們學習了LitePal的基本用法,體驗了使用框架來進行建立表操作的便利。然而大家都知道,建立表只是資料庫操作中最基本的一步而已,我們在一開始建立的表結構,隨著需求的變更,到了後期是極有可能需要修改的。因此,升級表的操作對於任何一個專案也是至關重要的,那麼今天我們就一起來學習一下,在Android傳統開發當中升級表的方式,以及使用LitePal來進行升級表操作的用法。如果你還沒有看過前一篇文章,建議先去參考一下 Android資料庫高手祕籍(2):建立表和LitePal的基本用法 。
LitePal的專案地址是:https://github.com/LitePalFramework/LitePal
傳統的升級表方式
上一篇文章中我們藉助MySQLiteHelper已經建立好了news這張表,這也是demo.db這個資料庫的第一個版本。然而,現在需求發生了變更,我們的軟體除了能看新聞之外,還應該允許使用者評論,所以這時就需要對資料庫進行升級,新增一個comment表。
該怎麼做呢?新增一個comment表的建表語句,然後在onCreate()方法中去執行它?沒錯,這樣的話,兩張表就會同時建立了,程式碼如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public class MySQLiteHelper extends SQLiteOpenHelper { public static final String CREATE_NEWS = "create table news (" + "id integer primary key autoincrement, " + "title text, " + "content text, " + "publishdate integer," + "commentcount integer)"; public static final String CREATE_COMMENT = "create table comment (" + "id integer primary key autoincrement, " + "content text)"; public MySQLiteHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_NEWS); db.execSQL(CREATE_COMMENT); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } } |
這對於第一次安裝我們軟體的使用者來說是完全可以正常工作的,但是如果有的使用者已經安裝過上一版的軟體,那麼很遺憾,comment表是建立不出來的,因為之前資料庫就已經建立過了,onCreate()方法是不會重新執行的。
對於這種情況我們就要用升級的方式來解決了,看到MySQLiteHelper構造方法中的第四個引數了嗎,這個就是資料庫版本號的標識,每當版本號增加的時候就會呼叫onUpgrade()方法,我們只需要在這裡處理升級表的操作就行了。比較簡單粗暴的方式是將資料庫中現有的所有表都刪除掉,然後重新建立,程式碼如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class MySQLiteHelper extends SQLiteOpenHelper { ...... @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_NEWS); db.execSQL(CREATE_COMMENT); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("drop table if exists news"); onCreate(db); } } |
可以看到,當資料庫升級的時候,我們先把news表刪除掉,然後重新執行了一次onCreate()方法,這樣就保證資料庫中的表都是最新的了。
但是,如果news表中本來已經有資料了,使用這種方式升級的話,就會導致表中的資料全部丟失,所以這並不是一種值得推薦的升級方法。那麼更好的升級方法是什麼樣的呢?這就稍微有些複雜了,需要在onUpgrade()方法中根據版本號加入具體的升級邏輯,我們來試試來吧。比如之前的資料庫版本號是1,那麼在onUpgrade()方法中就可以這樣寫:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class MySQLiteHelper extends SQLiteOpenHelper { ...... @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_NEWS); db.execSQL(CREATE_COMMENT); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { switch (oldVersion) { case 1: db.execSQL(CREATE_COMMENT); default: } } } |
可以看到,這裡在onUpgrade()方法中加入了一個switch判斷,如果oldVersion等於1,就再建立一個comment表。現在只需要呼叫如下程式碼,表就可以得到建立或升級了:
1 2 |
SQLiteOpenHelper dbHelper = new MySQLiteHelper(this, "demo.db", null, 2); SQLiteDatabase db = dbHelper.getWritableDatabase(); |
這裡我們將版本號加1,如果使用者是從舊版本升級過來的,就會新增一個comment表,而如果使用者是直接安裝的新版本,就會在onCreate()方法中把兩個表一起建立了。
OK,現在軟體的第二版本也釋出出去了,可是就在釋出不久之後,突然發現comment表中少了一個欄位,我們並沒有記錄評論釋出的時間。沒辦法,只好在第三版中修復這個問題了,那我們該怎麼樣去新增這個欄位呢?主要需要修改comment表的建表語句,以及onUpgrade()方法中的邏輯,程式碼如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class MySQLiteHelper extends SQLiteOpenHelper { ...... public static final String CREATE_COMMENT = "create table comment (" + "id integer primary key autoincrement, " + "content text, " + "publishdate integer)"; ...... @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { switch (oldVersion) { case 1: db.execSQL(CREATE_COMMENT); break; case 2: db.execSQL("alter table comment add column publishdate integer"); break; default: } } } |
可以看到,在建表語句當中我們新增了publishdate這一列,這樣當執行onCreate()方法去建立表的時候,comment表中就會有這一列了。那麼如果是從舊版本升級過來的呢?也沒有問題,我們在onUpgrade()方法中已經把升級邏輯都處理好了,當oldVersion等於2的時候,會執行alter語句來新增publishdate這一列。現在呼叫以下程式碼來建立或升級資料庫:
1 2 |
SQLiteOpenHelper dbHelper = new MySQLiteHelper(this, "demo.db", null, 3); SQLiteDatabase db = dbHelper.getWritableDatabase(); |
將資料庫版本號設定成3,這樣就可以保證資料庫中的表又是最新的了。
現在我們已經學習了新增表和新增列這兩種升級方式,那麼如果是某張表中的某一列已經沒有用了,我想把這一列刪除掉該怎麼寫呢?很遺憾,SQLite並不支援刪除列的功能,對於這情況,多數軟體採取的作法是無視它,反正以後也用不到它了,留著也佔不了什麼空間,所以針對於這種需求,確實沒什麼簡單的解決辦法。
這大概就是傳統開發當中升級資料庫表的方式了,雖說能寫出這樣的程式碼表示你已經對資料庫的升級操作理解的比較清楚了,但隨著版本越來越多,onUpgrade()方法中的邏輯也會變得愈發複雜,稍微一不留神,也許就會產生錯誤。因此,如果能讓程式碼自動控制升級邏輯,而不是由人工來管理,那就是再好不過了,那麼下面我們就來學習一下怎樣使用LitePal來進行升級表的操作。
使用LitePal升級表
通過上一篇文章的學習,我們已經知道LitePal是一款ORM模式的框架了,已經熟悉建立表流程的你,相信對於升級表也一定會輕車熟路的。那麼為了模仿傳統升級表方式中的需求,現在我們也需要建立一張comment表。第一步該怎麼辦呢?相信你也許早就已經猜到了,那當然是先建立一個Comment類了,如下所示:
1 2 3 4 5 6 7 8 9 10 11 |
package com.example.databasetest.model; public class Comment { private int id; private String content; // 自動生成get、set方法 ... } |
OK,Comment類中有id和content這兩個欄位,也就意味著comment表中會有id和content這兩列。
接著修改litepal.xml中的配置,在對映列表中新增Cooment類,並將版本號加1,如下所示:
1 2 3 4 5 6 7 8 9 10 11 |
<?xml version="1.0" encoding="utf-8"?> <litepal> <dbname value="demo" ></dbname> <version value="2" ></version> <list> <mapping class="com.example.databasetest.model.News"></mapping> <mapping class="com.example.databasetest.model.Comment"></mapping> </list> </litepal> |
沒錯,就是這麼簡單,僅僅兩步,升級的操作就已經完成了,現在我們只需要操作一下資料庫,comment表就會自動生成了,如下所示:
1 |
SQLiteDatabase db = Connector.getDatabase(); |
那麼我們還是通過.table命令來檢視一下結果,如下圖所示:
OK,comment表已經出來了,那麼再通過pragma命令來檢視一下它的表結構吧:
沒有問題,comment表中目前有id和content這兩列,和Comment模型類中的欄位是保持一致的。
那麼現在又來了新的需求,需要在comment表中新增一個publishdate列,該怎麼辦呢?不用懷疑,跟著你的直覺走,相信你已經猜到應該在Comment類中新增這樣一個欄位了吧,如下所示:
1 2 3 4 5 6 7 8 9 10 11 |
public class Comment { private int id; private String content; private Date publishDate; // 自動生成get、set方法 ... } |
後呢?剩下的操作已經非常簡單了,只需要在litepal.xml中對版本號加1就行了,如下所示:
1 2 3 4 5 6 |
<litepal> <dbname value="demo" ></dbname> <version value="3" ></version> ... </litepal> |
這樣當我們下一次運算元據庫的時候,publishdate列就應該會自動新增到comment表中。呼叫Connector.getDatabase()方法,然後重新查詢comment表結構,如下所示:
可以看到,publishdate這一列確實已經成功新增到comment表中了。
通過這兩種升級方式的對比,相信你已經充分體會到了使用LitePal進行升級表操作所帶來的便利了吧。我們不需要去編寫任何與升級相關的邏輯,也不需要關心程式是從哪個版本升級過來的,唯一要做的就是確定好最新的Model結構是什麼樣的,然後將litepal.xml中的版本號加1,所有的升級邏輯就都會自動完成了。LitePal確實將資料庫表的升級操作變得極度簡單,使很多程式設計師可以從維護資料庫表升級的困擾中解脫出來。
然而,LitePal卻明顯做到了更好。前面我們提到過關於刪除列的問題,最終的結論是無法解決,因為SQLite是不支援刪除列的命令的。但是如果使用LitePal,這一問題就可以簡單地解決掉,比如說publishdate這一列我們又不想要了,那麼只需要在Comment類中把它刪除掉,然後將版本號加1,下次運算元據庫的時候這個列就會不見了。
那麼有的朋友可能會問了,不是說SQLite不支援刪除列的命令嗎?那LitePal又是怎樣做到的呢?其實LitePal並沒有刪除任何一列,它只是先將comment表重新命名成一個臨時表,然後根據最新的Comment類的結構生成一個新的comment表,再把臨時表中除了publishdate之外的資料複製到新的表中,最後把臨時表刪掉。因此,看上去的效果好像是做到了刪除列的功能。
這也是使用框架的好處,如果沒有框架的幫助,我們顯然不會為了刪除一個列而大廢周章地去寫這麼多的程式碼,而使用框架的話,具體的實現邏輯我們已經不用再關心,只需要控制好模型類的資料結構就可以了。
另外,如果你想刪除某一張表的話,操作也很簡單,在litepal.xml中的對映列表中將相應的類刪除,表自然也就不存在了。其它的一些升級操作也都是類似的,相信你已經能舉一反三,這裡就不再贅述了。
好了,今天對LitePal的介紹就到這裡吧,下篇文章當中我們會學習使用LitePal來進行表關聯的操作,感興趣的朋友請繼續閱讀 Android資料庫高手祕籍(4):使用LitePal建立表關聯 。