1. 程式人生 > >Android 資料庫框架litepal的使用

Android 資料庫框架litepal的使用

來源:郭霖大神
http://blog.csdn.net/guolin_blog/article/details/38461239

要想熟練地操作任何一個數據庫,最最基本的要求就是要懂SQL語言,這也是每個程式設計師都應該掌握的技能。雖說SQL博大精深,要想精通確實很難,但最基本的一些建表命令,增刪改查,大家還是必須要學會的。

SQL(Structured Query Language)是一種標準的資料庫查詢語言,即所有的關係型資料庫都會支援它,只不過每種資料庫對SQL語言的支援與標準存在著細微的不同。我們無須關心其它資料庫對SQL語言的支援情況,這裡我們只要把重點放在SQLite上就可以了。下面我將使用模擬器來對SQLite支援的各種命令進行演示,如果你想用手機的話也可以,但要確保你的手機已經Root,並且包含sqlite3這個命令檔案。

首先確保模擬器已經連線上了電腦,然後在命令列輸入adb shell進入控制檯,如下圖所示:

3012329-554fceddb24c9f07

注意#符號表示我們當前已經是超級使用者了,如果顯示的是$符號,表示當前只是普通使用者而已,這時還需要輸入su命令切換一下使用者身份才行。

有了超級使用者許可權之後,我們能做的事情就很多了,這裡我們先檢視一下系統自帶的聯絡人表吧。進入到/data/data目錄下,如下圖所示:

3012329-c13b6dc419499da2

所有應用程式的本地儲存檔案都是存放在這個目錄下面的。為了要讓不同應用程式之間的資料容易區別開來,Android是使用應用程式包名進行分開管理,也就是說每個應用程式的本地儲存檔案都會存放在自己應用程式包名的那個目錄下,這裡我們ls一下看看有多少子目錄:

3012329-e3a475ae270aa471

OK,確實有很多,畢竟手機上所有的應用程式都在這裡。其中,com.android.providers.contacts中存放的就是聯絡人的相關資料,我們進入到這個目錄再ls一下:

3012329-cd2c148b5abe354a

可以看到,目前有databases、files、lib和shared_prefs這幾個子目錄。其中databases肯定是用於存放資料庫檔案的,files是用於存放普通文字檔案的,lib是用於存放so庫的,shared_prefs則是用於存放shared檔案的。這是Android資料持久化的幾種可選方式,對這部分內容不太瞭解的朋友可以參考《第一行程式碼——Android》的第六章。

接著進入到databases目錄中,再ls:

3012329-b48a19973dea8788

其中字尾名為journal的檔案是日誌檔案,我們不用管,contacts2.db和profile.db才是真正的資料庫檔案,可以使用sqlite3命令來開啟資料庫,如下圖所示:

3012329-fab36ef4e413ae11

好的,資料庫已經打開了,那麼我們怎麼才能知道當前資料庫中有哪些表呢?很簡單,.table命令就可以做到了:

3012329-44fa7c5b2067ae85

哇,竟然有這麼多張表!是的,聯絡人的資料結構非常複雜,很多的資料都是分表儲存的。這裡我們隨便挑一張表,比如說accounts表,如果我想知道這張表中有哪些列應該怎麼辦呢?在MySQL中可以使用desc accounts這個命令,但SQLite卻不認識這個命令,畢竟它們是有差異化的。SQLite中可以使用pragma table_info(TABLE_NAME)這個命令來查看錶的資料結構,如下圖所示:

3012329-6bbd79dc86458669

可以看到,一共顯示了三條結果,表示accounts表中共有三列。但是,所有的欄位都縮在了一行裡面,並用“|”符號分隔,這樣我們很難看出每個欄位的含義。很簡單,只需要換一種顯示模式就行了,比如說line模式就挺不錯的。輸入.mode line命令切換顯示模式,然後重新執行pragma命令,結果如下圖所示:

3012329-57d4da57c26aadb6

怎麼樣,這樣就清晰多了吧?這三列的列名分別是account_name、account_type和data_set,資料型別都是TEXT(字串),允許為空,並且都不是主鍵。好,那我現在想查一查accounts表中的資料呢?這就太簡單了,使用select語句就可以了,如下所示:

3012329-8181207ffde4c951

恩?怎麼只有一條空資料啊。貌似模擬器上預設就是這樣的,如果你用的是手機的話,這裡應該就可以查到真正的資料了。不過沒關係,我們可以在設定裡面手動新增一個郵箱賬戶,如下圖所示:

3012329-ba5502af9661588f

現在再來重新查詢一遍accounts表,如下所示:

3012329-6da50a7f2b10862c

OK,新增的新賬戶已經成功查出來了。

除了查詢命令之外,還有其它的增刪改命令都和標準的SQL語法是相同的,即insert、delete和update,由於比較簡單,我就不再贅述了。比較值得一提的是,每個SQLite資料庫中都還有一個隱藏的sqlite_master表,這裡記載了當前資料庫中所有表的建表語句,可以使用select * from sqlite_master命令進行檢視:

3012329-93a473a69dc3938c

結果太多了是不是?一屏根本就顯示不下嘛。不要著急,別忘了我們使用的是select命令,可以使用where語句來過濾出我們想要查詢的那部分內容,如下圖所示:

3012329-5c47419582fc66f4

OK,CREATE TABLE accounts (account_name TEXT, account_type TEXT, data_set TEXT) 這就是accounts表的建表語句了,通過這種方式我們可以查詢到任意一張表的建表語句,從而對我們學習和分析資料庫表結構有所幫助。

有些朋友可能會覺得,每次都要輸入select命令來查詢表中的資料太麻煩了。沒錯,而且還要保證手機是連線在電腦上的時候才能查詢,確實太不方便。幸運的是,有些手機軟體已經提供了資料庫表查詢的功能,使得我們隨時隨地都可以方便地檢視資料庫中的資料,比如Root Explorer這款軟體就不錯。

仍然是確保你的手機已經Root,然後安裝Root Explorer,開啟軟體之後按照我們前面介紹的路徑,進入/data/data/com.android.providers.contacts/databases,點選contacts2.db資料庫,選擇內建資料庫檢視器,然後隨便點選一張表就可以檢視到裡面的資料了,如下圖所示:

3012329-8ef159149c200012

使用這種方法,我們可以隨時檢視資料庫表中的最新資料,直觀又方便,在程式開發的時候可以起到非常大的幫助。

操作資料庫的第一步當然是建立表了,傳統建立表的方法相信大多數人都知道,那麼今天我除了會展示傳統的建表方法之外,還會講解LitePal這個框架的基本用法,並使用它來完成同樣的建表操作,讓大家體會到使用框架來操作資料庫的魅力。

那麼先來簡單介紹一下吧,LitePal是一款開源的Android資料庫框架,它採用了物件關係對映(ORM)的模式,並將我們平時開發時最常用到的一些資料庫功能進行了封裝,使得不用編寫一行SQL語句就可以完成各種建表、増刪改查的操作。並且LitePal很“輕”,jar包只有100k不到,而且近乎零配置,這一點和Hibernate這類的框架有很大區別。目前LitePal的原始碼已經託管到了GitHub上,地址是 https://github.com/LitePalFramework/LitePal

OK,簡單介紹完了LitePal,我們還是先來看一下,在傳統的Android開發中,需要怎麼去建立表。

傳統的建表方式

其實為了方便我們對資料庫表進行管理,Android本身就提供了一個幫助類:SQLiteOpenHelper。這個類集建立和升級資料庫於一身,並且自動管理了資料庫版本,算是一個非常好用的工具。

那我們現在就來試試SQLiteOpenHelper的用法吧。首先你要知道SQLiteOpenHelper是一個抽象類,這意味著如果我們想要使用它的話,就需要建立一個自己的幫助類去繼承它。SQLiteOpenHelper中有兩個抽象方法,分別是onCreate()和onUpgrade(),我們必須在自己的幫助類裡面重寫這兩個方法,然後分別在這兩個方法中去實現建立、升級資料庫的邏輯。本篇文章只需要把注意力放在建立資料庫這裡就行了,升級資料庫我們會在下一篇文章中去討論。

新建一個MySQLiteHelper類並讓它繼承SQLiteOpenHelper,這樣一個最基本的資料庫幫助類的程式碼如下所示:


public class MySQLiteHelper extends SQLiteOpenHelper {

    public MySQLiteHelper(Context context, String name, CursorFactory factory,
                          int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

其中,當資料庫建立的時候會呼叫onCreate()方法,在這裡去執行建表操作就可以了。比如說我們想新建一張news表,其中有title,content,publishdate,commentcount這幾列,分別代表著新聞標題、新聞內容、釋出時間和評論數,那麼程式碼就可以這樣寫:

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 MySQLiteHelper(Context context, String name, CursorFactory factory,
                          int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_NEWS);
    }  
        
}  

可以看到,我們把建表語句定義成了一個常量,然後在onCreate()方法中去執行了這條建表語句,news表也就建立成功了。這條建表語句雖然簡單,但是裡面還是包含了一些小的細節,我來解釋一下。首先,根據資料庫的正規化要求,任何一張表都應該是有主鍵的,所以這裡我們添加了一個自增長的id列,並把它設為主鍵。然後title列和content列都是字串型別的,commentcount列是整型的,這都很好理解,但是publishdate列該怎麼設計呢?由於SQLite中並不支援儲存日期這種資料型別,因此我們需要將日期先轉換成UTC時間(自1970年1月1號零點)的毫秒數,然後再儲存到資料庫中,因此publishdate列也應該是整型的。

現在,我們只需要獲取到SQLiteDatabase的例項,資料庫表就會自動建立了,如下所示:

SQLiteOpenHelper dbHelper = new MySQLiteHelper(this, "demo.db", null, 1);
SQLiteDatabase db = dbHelper.getWritableDatabase();

感覺很簡單很方便是嗎?那你就太容易滿足了,下面我們就來學習一下LitePal的基本用法,看一看使用這個框架是如何實現同樣的功能的。

LitePal的基本用法

雖說LitePal宣稱是近乎零配置,但也只是“近乎”而已,它還是需要進行一些簡單配置才可以使用的,那麼我們第一步就先快速學習一下LitePal的配置方法。

快速配置

1. 引入Jar包或原始碼

首先我們需要將LitePal的jar包引入到專案當中,可以點選這裡檢視LitePal的最新版本,選擇你需要的下載即可。下載好了jar包之後,把它複製到專案的libs目錄中就算是引入成功了,如下圖所示:

3012329-cfbdc40bb1a2af8b

如果你不想用jar包的話,也可以把LitePal的原始碼下載下來,然後作為一個library庫匯入到Eclipse當中,再讓我們的專案去引用這個library庫就可以了。

2. 配置litepal.xml

接著在專案的assets目錄下面新建一個litepal.xml檔案,並將以下程式碼拷貝進去:

SQLiteOpenHelper dbHelper = new MySQLiteHelper(this, "demo.db", null, 1);
SQLiteDatabase db = dbHelper.getWritableDatabase();

配置檔案相當簡單,<dbname>用於設定資料庫的名字,<version>用於設定資料庫的版本號,<list>用於設定所有的對映模型,我們稍後就會用到。

3. 配置LitePalApplication

由於操作資料庫時需要用到Context,而我們顯然不希望在每個介面中都去傳一遍這個引數,那樣操作資料庫就顯得太繁瑣了。因此,LitePal使用了一個方法來簡化掉Context這個引數,只需要在AndroidManifest.xml中配置一下LitePalApplication,所有的資料庫操作就都不用再傳Context了,如下所示:

SQLiteOpenHelper dbHelper = new MySQLiteHelper(this, "demo.db", null, 1);
SQLiteDatabase db = dbHelper.getWritableDatabase();

當然,有些程式可能會有自己的Application,並在這裡配置過了。比如說有一個MyApplication,如下所示:

SQLiteOpenHelper dbHelper = new MySQLiteHelper(this, "demo.db", null, 1);
SQLiteDatabase db = dbHelper.getWritableDatabase();

沒有關係,這時只需要修改一下MyApplication的繼承結構,讓它不要直接繼承Application類,而是繼承LitePalApplication類,就可以使用一切都能正常工作了,程式碼如下所示:

SQLiteOpenHelper dbHelper = new MySQLiteHelper(this, "demo.db", null, 1);
SQLiteDatabase db = dbHelper.getWritableDatabase();

但是,有些程式可能會遇到一些更加極端的情況,比如說MyApplication需要繼承另外一個AnotherApplication,並且這個AnotherApplication還是在jar包當中的,不能修改它的程式碼。這種情況應該算是比較少見了,但是如果你遇到了的話也不用急,仍然是有解釋方案的。你可以把LitePal的原始碼下載下來,然後把src目錄下的所有程式碼直接拷貝到你專案的src目錄下面,接著開啟LitePalApplication類,將它的繼承結構改成繼承自AnotherApplication,再讓MyApplication繼承自LitePalApplication,這樣所有的Application就都可以在一起正常工作了。

僅僅三步,我們就將所有的配置工作全部完成了,並且這是一件一本萬利的事情,自此以後,你就可以開心地體驗LitePal提供的各種便利了,就讓我們從建表開始吧。

開始建表

前面在介紹的時候已經說了,LitePal採取的是物件關係對映(ORM)的模式,那麼什麼是物件關係對映呢?簡單點說,我們使用的程式語言是面嚮物件語言,而我們使用的資料庫則是關係型資料庫,那麼將面向物件的語言和麵向關係的資料庫之間建立一種對映關係,這就是物件關係映射了。

但是我們為什麼要使用物件關係對映模式呢?這主要是因為大多數的程式設計師都很擅長面向物件程式設計,但其中只有少部分的人才比較精通關係型資料庫。而且資料庫的SQL語言晦澀難懂,就算你很精通它,恐怕也不喜歡經常在程式碼中去寫它吧?而物件關係對映模式則很好地解決了這個問題,它允許我們使用面向物件的方式來操作資料庫,從而可以從晦澀難懂的SQL語言中解脫出來。

那麼接下來我們就看一看LitePal中是如何建表的吧。根據物件關係對映模式的理念,每一張表都應該對應一個模型(Model),也就是說,如果我們想要建一張news表,就應該有一個對應的News模型類。新建一個News類,如下所示:

SQLiteOpenHelper dbHelper = new MySQLiteHelper(this, "demo.db", null, 1);
SQLiteDatabase db = dbHelper.getWritableDatabase();

然後,表中的每一列其實就是對應了模型類中的一個欄位,比如news表中有id、title、content、publishdate、commentcount這幾個列,那麼在News類中就也應該有這幾個欄位,程式碼如下所示:

SQLiteOpenHelper dbHelper = new MySQLiteHelper(this, "demo.db", null, 1);
SQLiteDatabase db = dbHelper.getWritableDatabase();

其中id這個欄位可寫可不寫,因為即使不寫這個欄位,LitePal也會在表中自動生成一個id列,畢竟每張表都一定要有主鍵的嘛。

這裡我要特別說明一下,LitePal的對映規則是非常輕量級的,不像一些其它的資料庫框架,需要為每個模型類單獨配置一個對映關係的XML,LitePal的所有對映都是自動完成的。根據LitePal的資料型別支援,可以進行物件關係對映的資料型別一共有8種,int、short、long、float、double、boolean、String和Date。只要是宣告成這8種資料型別的欄位都會被自動對映到資料庫表中,並不需要進行任何額外的配置。

現在模型類已經建好了,我們還差最後一步,就是將它配置到對映列表當中。編輯assets目錄下的litepal.xml檔案,在<list>標籤中加入News模型類的宣告:

SQLiteOpenHelper dbHelper = new MySQLiteHelper(this, "demo.db", null, 1);
SQLiteDatabase db = dbHelper.getWritableDatabase();

注意這裡一定要填入News類的完整類名。

OK,這樣所有的工作就都已經完成了,現在只要你對資料庫有任何的操作,news表就會被自動創建出來。比如說LitePal提供了一個便捷的方法來獲取到SQLiteDatabase的例項,如下所示:

SQLiteOpenHelper dbHelper = new MySQLiteHelper(this, "demo.db", null, 1);
SQLiteDatabase db = dbHelper.getWritableDatabase();

呼叫一下上述程式碼,news表就應該已經建立成功了。我們使用在上一篇文章中學到的SQLite命令來檢視一下,開啟demo.db資料庫,輸入.table命令,結果如下圖所示:

3012329-3fd21a171ff716d6

可以看到,news表已經存在了。另外兩張android_metadata和table_schema表是自動生成的,我們不用理。接下來我們還可以再查詢一下news表的建表語句,如下圖所示:

3012329-587c6cf8044b792f

這就是LitePal根據News類中的欄位自動幫我們生成的建表語句,由此也說明,建表操作已經成功完成了。

傳統的升級表方式

上面我們藉助MySQLiteHelper已經建立好了news這張表,這也是demo.db這個資料庫的第一個版本。然而,現在需求發生了變更,我們的軟體除了能看新聞之外,還應該允許使用者評論,所以這時就需要對資料庫進行升級,新增一個comment表。

該怎麼做呢?新增一個comment表的建表語句,然後在onCreate()方法中去執行它?沒錯,這樣的話,兩張表就會同時建立了,程式碼如下所示:


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()方法,我們只需要在這裡處理升級表的操作就行了。比較簡單粗暴的方式是將資料庫中現有的所有表都刪除掉,然後重新建立,程式碼如下所示:


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()方法中就可以這樣寫:

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表。現在只需要呼叫如下程式碼,表就可以得到建立或升級了:


SQLiteOpenHelper dbHelper = new MySQLiteHelper(this, "demo.db", null, 2);
SQLiteDatabase db = dbHelper.getWritableDatabase();

這裡我們將版本號加1,如果使用者是從舊版本升級過來的,就會新增一個comment表,而如果使用者是直接安裝的新版本,就會在onCreate()方法中把兩個表一起建立了。

OK,現在軟體的第二版本也釋出出去了,可是就在釋出不久之後,突然發現comment表中少了一個欄位,我們並沒有記錄評論釋出的時間。沒辦法,只好在第三版中修復這個問題了,那我們該怎麼樣去新增這個欄位呢?主要需要修改comment表的建表語句,以及onUpgrade()方法中的邏輯,程式碼如下所示:


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這一列。現在呼叫以下程式碼來建立或升級資料庫:


SQLiteOpenHelper dbHelper = new MySQLiteHelper(this, "demo.db", null, 3);
SQLiteDatabase db = dbHelper.getWritableDatabase();

將資料庫版本號設定成3,這樣就可以保證資料庫中的表又是最新的了。

現在我們已經學習了新增表和新增列這兩種升級方式,那麼如果是某張表中的某一列已經沒有用了,我想把這一列刪除掉該怎麼寫呢?很遺憾,SQLite並不支援刪除列的功能,對於這情況,多數軟體採取的作法是無視它,反正以後也用不到它了,留著也佔不了什麼空間,所以針對於這種需求,確實沒什麼簡單的解決辦法。

這大概就是傳統開發當中升級資料庫表的方式了,雖說能寫出這樣的程式碼表示你已經對資料庫的升級操作理解的比較清楚了,但隨著版本越來越多,onUpgrade()方法中的邏輯也會變得愈發複雜,稍微一不留神,也許就會產生錯誤。因此,如果能讓程式碼自動控制升級邏輯,而不是由人工來管理,那就是再好不過了,那麼下面我們就來學習一下怎樣使用LitePal來進行升級表的操作。

使用LitePal升級表

通過上一篇文章的學習,我們已經知道LitePal是一款ORM模式的框架了,已經熟悉建立表流程的你,相信對於升級表也一定會輕車熟路的。那麼為了模仿傳統升級表方式中的需求,現在我們也需要建立一張comment表。第一步該怎麼辦呢?相信你也許早就已經猜到了,那當然是先建立一個Comment類了,如下所示:


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,如下所示:


<?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表就會自動生成了,如下所示:

SQLiteDatabase db = Connector.getDatabase();

那麼我們還是通過.table命令來檢視一下結果,如下圖所示:

3012329-04552b7607ff8178

OK,comment表已經出來了,那麼再通過pragma命令來檢視一下它的表結構吧:

3012329-c92cd8b4ac292130

沒有問題,comment表中目前有id和content這兩列,和Comment模型類中的欄位是保持一致的。

那麼現在又來了新的需求,需要在comment表中新增一個publishdate列,該怎麼辦呢?不用懷疑,跟著你的直覺走,相信你已經猜到應該在Comment類中新增這樣一個欄位了吧,如下所示:

public class Comment {  
      
    private int id;  
      
    private String content;  
      
    private Date publishDate;  
      
    // 自動生成get、set方法   
    ...  
}  

然後呢?剩下的操作已經非常簡單了,只需要在litepal.xml中對版本號加1就行了,如下所示:

<litepal>  
    <dbname value="demo" ></dbname>  
  
    <version value="3" ></version>  
    ...  
</litepal>

這樣當我們下一次操作資料庫的時候,publishdate列就應該會自動新增到comment表中。呼叫Connector.getDatabase()方法,然後重新查詢comment表結構,如下所示:

3012329-bbe314ca90618c77

可以看到,publishdate這一列確實已經成功新增到comment表中了。

通過這兩種升級方式的對比,相信你已經充分體會到了使用LitePal進行升級表操作所帶來的便利了吧。我們不需要去編寫任何與升級相關的邏輯,也不需要關心程式是從哪個版本升級過來的,唯一要做的就是確定好最新的Model結構是什麼樣的,然後將litepal.xml中的版本號加1,所有的升級邏輯就都會自動完成了。LitePal確實將資料庫表的升級操作變得極度簡單,使很多程式設計師可以從維護資料庫表升級的困擾中解脫出來。

然而,LitePal卻明顯做到了更好。前面我們提到過關於刪除列的問題,最終的結論是無法解決,因為SQLite是不支援刪除列的命令的。但是如果使用LitePal,這一問題就可以簡單地解決掉,比如說publishdate這一列我們又不想要了,那麼只需要在Comment類中把它刪除掉,然後將版本號加1,下次操作資料庫的時候這個列就會不見了。

那麼有的朋友可能會問了,不是說SQLite不支援刪除列的命令嗎?那LitePal又是怎樣做到的呢?其實LitePal並沒有刪除任何一列,它只是先將comment表重新命名成一個臨時表,然後根據最新的Comment類的結構生成一個新的comment表,再把臨時表中除了publishdate之外的資料複製到新的表中,最後把臨時表刪掉。因此,看上去的效果好像是做到了刪除列的功能。

這也是使用框架的好處,如果沒有框架的幫助,我們顯然不會為了刪除一個列而大廢周章地去寫這麼多的程式碼,而使用框架的話,具體的實現邏輯我們已經不用再關心,只需要控制好模型類的資料結構就可以了。

另外,如果你想刪除某一張表的話,操作也很簡單,在litepal.xml中的對映列表中將相應的類刪除,表自然也就不存在了。其它的一些升級操作也都是類似的,相信你已經能舉一反三,這裡就不再贅述了。

關聯關係的基礎知識

喜歡把所有的程式碼都寫在一個類裡的程式設計師肯定是個新手。沒錯,任何一個像樣的程式都不可能僅僅只有一個類的,同樣地,任何一個像樣的資料庫也不可能僅僅只有一張表。我們都知道,在面向物件的程式語言中,多個類之間可以相互關聯引用,共同完成某項功能。那麼在資料庫當中,多個表之間可以相互關聯嗎?當然可以!只不過表與表之間的關聯關係要比物件之間的關聯關係複雜一些,也更加難懂,但是作為資料庫的基本功,還是應該瞭解清楚的,那麼我們就先來學習一下資料庫表關聯的基礎知識。

表與表之間的關聯關係一共有三種類型,一對一、多對一、和多對多,下面我們分別對這三種類型展開進行討論。

一對一

表示兩個表中的資料必須是一一對應的關係。這種場景其實並不是很常見,我們還是通過例子來直觀地體會一下,例子仍然是在之前文章的基礎上展開的。

現在我們已經建立好了news這張表,裡面主要記錄了新聞的標題和內容,那麼除了標題和內容之外,有些新聞還可能帶有一些導語和摘要,我們把這兩個欄位放在一張introduction表中,作為新聞的簡介。那麼很顯然,news表和introduction表就是一對一的關係了,因為一條新聞只能對應一個簡介,一個簡介也只能屬於一條新聞。它們之間的對應關係大概如下圖描述的一樣:

3012329-c78fb02881be6ace

可以看到,News1對應了Introduction2,News2對應了Introduction3,News3對應了Introduction1,但不管怎麼樣,它們都是一對一的關係。

那麼這種一對一的關係,在程式語言中該怎麼體現出來呢?相信熟悉面向物件設計的你,一定很輕鬆就能想出來吧,只需要在News類中持有一個Introduction類的引用,然後在Introduction類中也持有一個News類的引用,這樣它們之間自然就是一對一的關係了。

沒錯,物件之間的一對一關係非常簡單易懂,那麼難點就在於,如何在資料庫表中建立這樣的一對一關係了。由於資料庫並不像面向物件的語言一樣支援相互引用,如果想讓兩張表之間建立一對一的關係,一般就只能通過外來鍵的方式來實現了。因此,一對一關係的表結構就可以這樣設計:

3012329-965529a6a137fff8

請注意,introduction表中有一個news_id列,這是一個外來鍵列,裡面應該存放一個具體的新聞id,這樣一條introduction就能對應一條news,也就實現一對一的關係了,如下圖所示:

3012329-f1486c2dd0c186ad

由此我們就能夠看出,id為1的introduction對應著id為2的news,id為2的introduction對應著id為3的news,id為3的introduction對應著id為1的news。需要注意的是,一對一的關係並沒有強制要求外來鍵必須加在哪一張表上,你可以在introduction表中加一個news_id作為外來鍵,也可以在news表中加一個introduction_id作為外來鍵,不管使用哪一種,都可以表示出它們是一對一的關聯關係。

多對一

表示一張表中的資料可以對應另一張表中的多條資料。這種場景比起一對一關係就要常見太多了,在我們平時的開發工作中多對一關係真的是比比皆是。比如說現在我們的資料庫中有一個news表,還有一個comment表,它們兩個之間就是典型的多對一關係,一條新聞可以有很多條評論,但是一條評論只能是屬於一條新聞的。它們的關係如下圖所示:

3012329-97271dcc5b3ceeb8

而這種多對一的關係在程式語言中是非常容易體現出來的,比如Java中就有專門集合類,如List、Set等,使用它們的話就能輕鬆簡單地在物件之間建立多對一的關係,我們稍後就會看到。那麼,這裡的難點仍然是在資料庫表中如何建立這樣的多對一關係。現在說難點其實已經不難了,因為前面我們已經學會了一對一關係的建立方法,而多對一也是類似的。沒錯,資料庫表中多對一的關係仍然是通過外來鍵來建立的,只不過一對一的時候外來鍵加在哪一張表上都可以,但多對一的時候關鍵必須要加在多方的表中。因此,多對一關係的表結構就可以這樣設計:

3012329-4c777af496e47a54

在comment表中有一個news_id列,這是一個外來鍵列,裡面應該存放一個具體的新聞id,並且允許多條comment都存放同一個新聞id,這樣一條評論就只能對應一條新聞,但一條新聞卻可以有多條評論,也就實現多對一的關係了,如下圖所示:

3012329-9e0d609a1738428e

由此我們就可以看出,id為1、2、3的三條評論是屬於第一條新聞的,而id為4、5的兩條評論是屬於第二條新聞的。

多對多

表示兩張關聯表中的資料都可以對應另一張表中的多條資料。這種場景也不算是很常見,但比一對一關係要稍微更加常用一些。舉個例子,我們都知道新聞網站是會將新聞進行種類劃分的,這樣使用者就可以選擇自己喜歡的那一類新聞進行瀏覽,比如說網易新聞中就會有頭條、科技、娛樂、手機等等種類。每個種類下面當然都會有許多條新聞,而一條新聞也可能是屬於多個種類的,比如iPhone6釋出的新聞既可以屬於手機種類,也可以屬於科技種類,甚至還可以上頭條。因此,新聞和種類之間就是一種多對多的關係,如下圖所示:

3012329-2ae6e270b6da0c98

可以看到,News1是屬於Category1的,而News2和News3都是既屬於Category1也屬於Category2,如此複雜的關聯關係該如何表示呢?在面向物件的程式語言中一切都是那麼的簡單,只需要在News類中使用集合類宣告擁有多個Category,然後在Category類中也使用集合類宣告擁有多個News就可以了,我們稍後就會看到。而難點仍然是留在了資料庫上,兩張表之間如何建立多對多的關聯關係呢,還是用外來鍵嗎?肯定不行了,多對多的情況只能是藉助中間表來完成了。也就是說,我們需要多建立一張表,這張表沒什麼其它作用,就是為了存放news表和category表之間的關聯關係的,如下圖所示:

3012329-d4e052119727f137

注意這裡我們建立一張名為category_news的中間表,中間表的命名並沒有什麼強制性的約束,但一個良好的命名規範可以讓你一眼就明白這張表是用來做什麼的。中間表裡面只有兩列,而且也只需要有兩列,分別是news表的外來鍵和category表的外來鍵,在這裡存放新聞和種類相應的id,就可以讓它們之間建立關聯關係了,如下圖所示:

3012329-8369d84b1d7b590e

由此我們就可以看出,第一條新聞是屬於第一個種類的,而第二和第三條新聞,則既屬於第一個種類,也屬於第二個種類。反過來也可以這樣看,第一個種類下面有第一、第二、第三這三條新聞,而第二個種類下面只有第二、第三這兩條新聞。不管怎麼看,多對多的關係都是成立的。

好了,三種關聯關係都講完了,那我們來簡單總結一下吧。雖說上面介紹了花了很大的篇幅講解資料庫的表關聯知識,但其實最後的結論是非常簡單的,大家可以當成口訣一樣背下來。即一對一關聯的實現方式是用外來鍵,多對一關聯的實現方式也是用外來鍵,多對多關聯的實現方式是用中間表。記下了這個口訣,在很多資料庫設計的時候,你都可以發揮得更加遊刃有餘。

使用LitePal建立表關聯

雖說口訣就是這個樣子,但牽扯到表關聯的時候畢竟增加了建表的難度,建表語句會更加複雜,你也需要格外地小心以防止出現什麼錯誤。因此,使用LitePal來自動建立表關聯又是一個非常不錯的選擇,我們不需要關心什麼外來鍵、中間表等實現的細節,只需要在物件中宣告好它們相互之間的引用關係,LitePal就會自動在資料庫表之間建立好相應的關聯關係了,下面我們就來嘗試一下吧。

首先確定一下一共涉及到了哪些實體類,News和Comment,這兩個類我們在前兩篇文章中就已經建好了,然後還需要有Introduction和Category這兩個類,新建Introduction類,程式碼如下所示:

public class Introduction {  
      
    private int id;  
      
    private String guide;  
      
    private String digest;  
      
    // 自動生成get、set方法  
} 

接著新建Category類,程式碼如下所示:

public class Category {  
      
    private int id;  
      
    private String name;  
      
    // 自動生成get、set方法  
}  

現在四個類都已經建好了,但目前它們都還是各自獨立的,互相之間沒有任何聯絡,那麼我們現在就開始用極為簡單易懂的方式來給它們建立關聯吧。首先,News和Introduction是一對一的關係,那就可以在News類中新增如下引用:

public class News {  
    ...  
    private Introduction introduction;  
      
    // 自動生成get、set方法  
}  

就是這麼簡單,在News類中可以得到一個對應的Introduction的例項,那麼它們之間就是一對一關係了。

接著Comment和News是多對一的關係,因此News中應該包含多個Comment,而Comment中應該只有一個News,所以就可以這樣寫:

public class News {  
    ...  
    private Introduction introduction;  
      
    private List<Comment> commentList = new ArrayList<Comment>();  
      
    // 自動生成get、set方法  
}  

先使用一個泛型為Comment的List集合來表示News中包含多個Comment,然後修改Comment類的程式碼,如下所示:

public class Comment {  
    ...  
    private News news;  
      
    // 自動生成get、set方法   
}  

在Comment類中聲明瞭一個News的例項,這樣就清楚地表示出了News中可以包含多個Comment,而Comment中只能有一個News,也就是多對一的關係了。

最後News和Category是多對多的關係,相信聰明的你一定已經知道該怎麼寫了。News中可以包含多個Category,所以仍然應該使用List集合來表示:

public class News {  
    ...  
    private Introduction introduction;  
      
    private List<Comment> commentList = new ArrayList<Comment>();  
      
    private List<Category> categoryList = new ArrayList<Category>();  
      
    // 自動生成get、set方法  
}  

而Category中也可以包含多個News,因此Category類也應該使用相同的寫法,如下所示:

public class Category {  
    ...  
    private List<News> newsList = new ArrayList<News>();  
      
    // 自動生成get、set方法  
} 

這樣就清楚地表達出它們之間是多對多的關聯了。

關聯關係都宣告好了之後,我們只需要將所有的實體類都新增到對映列表當中,並將資料庫版本號加1就可以了。修改litepal.xml的程式碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>  
<litepal>  
    <dbname value="demo" ></dbname>  
  
    <version value="4" ></version>  
  
    <list>  
        <mapping class="com.example.databasetest.model.News"></mapping>  
        <mapping class="com.example.databasetest.model.Comment"></mapping>  
        <mapping class="com.example.databasetest.model.Introduction"></mapping>  
        <mapping class="com.example.databasetest.model.Category"></mapping>  
    </list>  
</litepal>  

基本上到這裡就可以輕鬆地說結束了,現在只需要任意操作一下資料庫,表之間的關聯關係就將會自動建立,比如說呼叫一下Connector.getDatabase()方法。

下面我們來驗證一下吧,輸入.table命令檢視一下當前資料庫中的表,如下所示:

3012329-2b7c6fd85d810e62

OK,news、comment、category、introduction這幾張表全都有了,除此之外還有一張category_news中間表。那我們要來一一檢查一下了,先檢視一下introduction表的結構吧,如下所示:

3012329-0dcd392c81e78b46

可以看到,多了一個news_id列,說明introduction表和news表之間的一對一關係已經建立好了。

然後再檢查一下comment表的結構,如下所示:

3012329-f31df196cf6165a2

OK,comment表中也有一個news_id的列,那麼comment表和news表之間的多對一關係也已經建立好了。

最後檢查一下category_news這張中間表的結構,如下所示:

3012329-f1651934c8312335

一共只有兩列,一列是news_id,一列是category_id,分別對應著兩張表的外來鍵,這樣news表和category表的多對多關係也建立好了。

傳統的儲存資料方式

其實最傳統的儲存資料方式肯定是通過SQL語句拼接字串來進行儲存的,不過這種方式有點過於“傳統”了,今天我們在這裡就不討論這種情況。實際上,Android專門提供了一種用於儲存資料的簡便方法,使得我們不用編寫SQL語句就可以執行儲存操作。下面來看一下SQLiteDatabase中的insert()方法:

public long insert(String table, String nullColumnHack, ContentValues values)  

可以看到,insert方法接收三個引數,第一個引數是表名,第二個引數通常都用不到,直接傳null,第三個引數則是一個封裝了待儲存資料的ContentValues物件。因此,比如說我們想往news表中插入一條新聞,就可以這樣寫:

SQLiteDatabase db = dbHelper.getWritableDatabase();  
ContentValues values = new ContentValues();  
values.put("title", "這是一條新聞標題");  
values.put("content", "這是一條新聞內容");  
values.put("publishdate", System.currentTimeMillis());  
long id = db.insert("news", null, values);

其中,呼叫ContentValues的put()方法來新增待儲存資料,put()方法接收兩個引數,第一個引數是資料庫表中對應的列名,第二個引數就是要儲存的值,最後呼叫一下insert()方法,這條新聞就會插入到news表當中了,並且該資料行對應的id會作為返回值進行返回。

用法很簡單是嗎?確實,比起直接使用SQL語句,SQLiteDatabase中提供的insert()方法的確簡單了很多。但insert()方法也並非是那麼的完美,它還是有很多不方便的地方的,比如說沒有考慮表關聯的情況,我們需要手動對關聯表的外來鍵進行儲存。再比如說,沒有提供批量儲存的功能,當我們有一個集合的資料需要儲存時,需要通過迴圈來遍歷這個集合,然後一次次地呼叫insert()方法來插入資料。

好了,那麼關於傳統儲存資料的用法就簡單介紹到這裡,因為確實沒什麼的更多的用法了,並且它也不是我們今天的主角。接下來,就讓我們看一看今天的驚喜,學習如何使用LitePal來進行資料庫儲存的操作。

使用LitePal儲存資料

LitePal中與儲存相關的API其實並不多,但用法還是頗為豐富的,而且比起傳統的insert()方法,使用LitePal來儲存資料可以簡單到讓你驚歎的地步,那麼今天我們就來完整地學習一下LitePal儲存資料的所有用法。

在前面幾篇文章當中,我們在專案裡已經建好了News、Comment、Introduction、Category這幾個實體類,通過這些實體類,LitePal就可以把相應的表自動創建出來。現在來觀察這幾個實體類,我們發現這幾個類都是沒有繼承結構的。沒錯,因為LitePal進行表管理操作時不需要這些實體類有任何的繼承結構,當時為了簡單起見就沒有寫。但是進行CRUD操作時就不行了,LitePal要求所有的實體類都要繼承自DataSupport這個類,因此這裡我們就要把繼承結構給加上才行。修改News類的程式碼,如下所示:

public class News extends DataSupport{  
      
    ......  
      
    // 自動生成get、set方法  
}  

可以看到,這裡只是讓News類繼承自了DataSupport,其它什麼都沒有改變。另外幾個Comment、Introduction、Category類也使用同樣的改法,這裡就不一一演示了。

繼承了DataSupport類之後,這些實體類就擁有了進行CRUD操作的能力,那麼比如想要儲存一條資料到news表當中,就可以這樣寫:

News news = new News();  
news.setTitle("這是一條新聞標題");  
news.setContent("這是一條新聞內容");  
news.setPublishDate(new Date());  
news.save();  

怎麼樣?是不是非常簡單,不需要SQLiteDatabase,不需要ContentValues,不需要通過列名組裝資料,甚至不需要指定表名,只需要new出一個News物件,然後把要儲存的資料通過setter方法傳入,最後呼叫一下save()方法就好了,而這個save()方法自然就是從DataSupport類中繼承而來的了。

除此之外,save()方法還是有返回值的,我們可以根據返回值來判斷儲存是否成功,比如說這樣寫:

if (news.save()) {  
    Toast.makeText(context, "儲存成功", Toast.LENGTH_SHORT).show();  
} else {  
    Toast.makeText(context, "儲存失敗", Toast.LENGTH_SHORT).show();  
}  

可以看出,save()方法返回的是一個布林值,用於表示儲存成功還是失敗,但同時也說明這個方法是不會丟擲異常的。有些朋友希望如果儲存失敗的話就丟擲異常,而不是返回一個false,那就可以使用saveThrows()方法來代替,如下所示:

News news = new News();  
news.setTitle("這是一條新聞標題");  
news.setContent("這是一條新聞內容");  
news.setPublishDate(new Date());  
news.saveThrows();  

使用saveThrows()方法來儲存資料,一旦儲存失敗就會丟擲一個DataSupportException異常,我們可以通過對這個異常進行捕獲來處理儲存失敗的情況。

那有些細心的朋友可能已經注意到,使用的insert()方法來儲存資料時是有返回值的,返回的是插入行對應的id。但LitePal中的save()方法返回的是布林值,那麼我們怎樣才能拿到儲存成功之後這條資料對應的id呢?對此,LitePal使用了一種非常巧妙的做法,還記得我們在每個實體類中都定義了一個id欄位嗎?當呼叫save()方法或saveThrows()方法儲存成功之後,LitePal會自動將該條資料對應的id賦值到實體類的id欄位上。讓我們來做個試驗吧,程式碼如下所示:

News news = new News();  
news.setTitle("這是一條新聞標題");  
news.setContent("這是一條新聞內容");  
news.setPublishDate(new Date());  
Log.d("TAG", "news id is " + news.getId());  
news.save();  
Log.d("TAG", "news id is " + news.getId());  

在save之前列印一下news的id,在save之後再列印一次,現在執行一下,列印結果如下所示:

3012329-a2a0bbfe3c26c1ad

OK,在save之前列印的id是0,說明此時id這個欄位還沒有被賦值,在save之後列印的id是1,說明此時id已經被賦值了。那麼我們再到資料庫表中再檢視一下這條記錄到底有沒有儲存成功吧,如下圖所示:

3012329-f2f71f92d2ccc895

可以看到,這條新聞確實已經儲存成功了,並且對應的id正是1,和我們前面列印的結果是一致的。

不過LitePal的儲存功能顯示不僅僅只有這些用法,事實上,LitePal在儲存資料的時候默默幫我們做了很多的事情,比如多個實體類之間有關聯關係的話,我們不需要考慮在儲存資料的時候怎麼去建立資料與資料之間的關聯,因為LitePal一切都幫我們做好了。

還是通過一個例子來看一下吧,Comment和News之間是多對一的關係,一條News中是可以包含多條評論的,因此我們就可以這樣寫:

Comment comment1 = new Comment();  
comment1.setContent("好評!");  
comment1.setPublishDate(new Date());  
comment1.save();  
Comment comment2 = new Comment();  
comment2.setContent("贊一個");  
comment2.setPublishDate(new Date());  
comment2.save();  
News news = new News();  
news.getCommentList().add(comment1);  
news.getCommentList().add(comment2);  
news.setTitle("第二條新聞標題");  
news.setContent("第二條新聞內容");  
news.setPublishDate(new Date());  
news.setCommentCount(news.getCommentList().size());  
news.save();  

可以看到,這裡先是儲存了一條comment1資料,然後儲存一條comment2資料,接著在儲存News之前先把剛才的兩個Comment物件新增到了News的commentList列表當中,這樣就表示這兩條Comment是屬於這個News物件的,最後再把News儲存到資料庫中,這樣它們之間的關聯關係就會自動建立了。讓我們檢視資料庫表檢查一下吧,首先看一下news表,如下所示:

3012329-19c4af2c399282fe

OK,第二條新聞已經成功儲存到news表中了,這條新聞的id是2。那麼從哪裡可以看出來關聯關係呢?我們在上一篇文章中學過,多對一關聯的時候,外來鍵是存放在多方的,因此關聯關係我們要到comment表中去檢視,如下所示:

3012329-01dfb8bda53f6fe7

可以看到,兩條評論都已經成功儲存到comment表中了,並且這兩條評論的news_id都是2,說明它們是屬於第二條新聞的。怎麼樣,僅僅是在儲存資料之前建立好實體類之間的關係,再呼叫一下save()方法,那麼資料之間的關聯關係就會自動建立了,是不是非常簡單?上面的程式碼只是多對一情況的一種用法,還有一對一和多對多的情況,其實用法都是差不多的,相信你已經能舉一反三了。

另外,LitePal對集合資料的儲存還專門提供了一個方法,比如說我們有一個News集合,那麼應該怎樣去儲存這個集合中的每條News呢?傳統情況下可以這樣寫:

List<News> newsList;  
...  
for (News news : newsList) {  
    news.save();  
}  

通過一個迴圈來遍歷出這個集合中的每一個News物件,然後逐個呼叫save()方法。這樣的寫法當然是可以的,但是效率會比較低,因為呼叫save()方法的時候除了會執行儲存操作之外,還會去分析News類的關聯關係,那麼每次迴圈都去重新分析一遍關聯關係顯然是比較耗時的。因此,LitePal提供了一個saveAll()方法,專門用於儲存集合資料的,用法如下所示:

List<News> newsList;  
...  
DataSupport.saveAll(newsList);  

saveAll()方法接收一個Collection集合引數,只要把待儲存的集合資料傳入即可。這個方法可以完成和上面一段程式碼完全一樣的功能,但效率卻會高得多,而且寫法也更加簡單。

傳統的修改和刪除資料方式

上篇文章中我們已經得知,SQLiteDatabase類中提供了一個insert()方法用於插入資料,那麼類似地,它還提供了update()和delete()這兩個方法,分別用於修改和刪除資料。先來看一下update()方法的方法定義:

public int update(String table, ContentValues values, String whereClause, String[] whereArgs)  

update()方法接收四個引數,第一個引數是表名,第二個引數是一個封裝了待修改資料的ContentValues物件,第三和第四個引數用於指定修改哪些行,對應了SQL語句中的where部分。

那麼比如說我們想把news表中id為2的記錄的標題改成“今日iPhone6釋出”,就可以這樣寫:

SQLiteDatabase db = dbHelper.getWritableDatabase();  
ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6釋出");  
db.update("news", values, "id = ?", new String[] {"2"});  

其作用相當於如下SQL語句:

update news set title='今日iPhone6釋出' where id=2;  

可以看出,比起直接使用SQL語句,update()方法的語義性明顯更強,也更容易讓人理解。
接下來再看一下delete()方法的方法定義:

public int delete(String table, String whereClause, String[] whereArgs)  

delete()方法接收三個引數,第一個引數同樣是表名,第二和第三個引數用於指定刪除哪些行,對應了SQL語句中的where部分。

那麼比如說我們想把news表中所有沒有評論的新聞都刪除掉,就可以這樣寫:

SQLiteDatabase db = dbHelper.getWritableDatabase();  
db.delete("news", "commentcount = ?", new String[] {"0"}); 

其作用相當於如下SQL語句:

delete from news where commentcount=0;  

由此可見,Android給我們提供的這些幫助方法,在很大程度上確實簡化了不少資料庫操作的複雜度。不過LitePal顯然做到了更好,下面就讓我們學習一下如何使用LitePal來進行修改和刪除操作。

使用LitePal修改資料

LitePal修改資料的API比較簡單,並沒有什麼太多的用法,也比較好理解,方法都是定義在DataSupport類中的,我們先來看一下方法定義:

public static int update(Class<?> modelClass, ContentValues values, long id)  

這個靜態的update()方法接收三個引數,第一個引數是Class,傳入我們要修改的那個類的Class就好,第二個引數是ContentValues物件,這三個引數是一個指定的id,表示我們要修改哪一行資料。

那麼比如說我們想把news表中id為2的記錄的標題改成“今日iPhone6釋出”,就可以這樣寫:

ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6釋出");  
DataSupport.update(News.class, values, 2);  

可以看出,總體來講還是比原生的用法要簡單一些的,首先我們避免掉了要去獲取SQLiteDatabase物件的步驟,其次在指定修改某一條id記錄的時候只需要傳入這個id即可,語法更簡練。

那麼有的朋友可能會問了,也許我想修改的是某一個條件下的所有資料,而不是僅僅修改某個id的資料,那該怎麼辦呢?別擔心,LitePal還提供了另外一個簡便的方法,方法定義如下:

public static int updateAll(Class<?> modelClass, ContentValues values, String... conditions)  

updateAll()方法表示修改多行記錄,其中第一個引數仍然是Class,第二個引數還是ContentValues物件,第三個引數是一個conditions陣列,用於指定修改哪些行的約束條件,返回值表示此次修改影響了多少行資料。

那麼比如說我們想把news表中標題為“今日iPhone6釋出”的所有新聞的