經過前面幾篇文章的學習,我們已經把LitePal的表管理模組的功能都很好地掌握了,相信大家都已經體會到了使用LitePal來建立表、升級表、以及建立表關聯所帶來的便利。那麼從本篇文章開始,我們將進入到一個新模組的學習旅程當中,使用LitePal來進行表的CRUD操作。還沒有看過前一篇文章的朋友建議先去參考 Android資料庫高手祕籍(4):使用LitePal建立表關聯 。
LitePal提供的CRUD操作的API還是頗為豐富的,一篇文章肯定是介紹不全的,因此這裡我們仍然是分幾篇文章進行講解,本篇主要是介紹儲存方面的API。
LitePal的專案地址是:https://github.com/LitePalFramework/LitePal
傳統的儲存資料方式
其實最傳統的儲存資料方式肯定是通過SQL語句拼接字串來進行儲存的,不過這種方式有點過於“傳統”了,今天我們在這裡就不討論這種情況。實際上,Android專門提供了一種用於儲存資料的簡便方法,使得我們不用編寫SQL語句就可以執行儲存操作。下面來看一下SQLiteDatabase中的insert()方法:
1 |
public long insert(String table, String nullColumnHack, ContentValues values) |
可以看到,insert方法接收三個引數,第一個引數是表名,第二個引數通常都用不到,直接傳null,第三個引數則是一個封裝了待儲存資料的ContentValues物件。因此,比如說我們想往news表中插入一條新聞,就可以這樣寫:
1 2 3 4 5 6 |
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類的程式碼,如下所示:
1 2 3 4 5 6 |
public class News extends DataSupport{ ...... // 自動生成get、set方法 } |
可以看到,這裡只是讓News類繼承自了DataSupport,其它什麼都沒有改變。另外幾個Comment、Introduction、Category類也使用同樣的改法,這裡就不一一演示了。
繼承了DataSupport類之後,這些實體類就擁有了進行CRUD操作的能力,那麼比如想要儲存一條資料到news表當中,就可以這樣寫:
1 2 3 4 5 |
News news = new News(); news.setTitle("這是一條新聞標題"); news.setContent("這是一條新聞內容"); news.setPublishDate(new Date()); news.save(); |
怎麼樣?是不是非常簡單,不需要SQLiteDatabase,不需要ContentValues,不需要通過列名組裝資料,甚至不需要指定表名,只需要new出一個News物件,然後把要儲存的資料通過setter方法傳入,最後呼叫一下save()方法就好了,而這個save()方法自然就是從DataSupport類中繼承而來的了。
除此之外,save()方法還是有返回值的,我們可以根據返回值來判斷儲存是否成功,比如說這樣寫:
1 2 3 4 5 |
if (news.save()) { Toast.makeText(context, "儲存成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(context, "儲存失敗", Toast.LENGTH_SHORT).show(); } |
可以看出,save()方法返回的是一個布林值,用於表示儲存成功還是失敗,但同時也說明這個方法是不會丟擲異常的。有些朋友希望如果儲存失敗的話就丟擲異常,而不是返回一個false,那就可以使用saveThrows()方法來代替,如下所示:
1 2 3 4 5 |
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欄位上。讓我們來做個試驗吧,程式碼如下所示:
1 2 3 4 5 6 7 |
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之後再列印一次,現在執行一下,列印結果如下所示:
OK,在save之前列印的id是0,說明此時id這個欄位還沒有被賦值,在save之後列印的id是1,說明此時id已經被賦值了。那麼我們再到資料庫表中再檢視一下這條記錄到底有沒有儲存成功吧,如下圖所示:
可以看到,這條新聞確實已經儲存成功了,並且對應的id正是1,和我們前面列印的結果是一致的。
不過LitePal的儲存功能顯示不僅僅只有這些用法,事實上,LitePal在儲存資料的時候默默幫我們做了很多的事情,比如多個實體類之間有關聯關係的話,我們不需要考慮在儲存資料的時候怎麼去建立資料與資料之間的關聯,因為LitePal一切都幫我們做好了。
還是通過一個例子來看一下吧,Comment和News之間是多對一的關係,一條News中是可以包含多條評論的,因此我們就可以這樣寫:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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表,如下所示:
OK,第二條新聞已經成功儲存到news表中了,這條新聞的id是2。那麼從哪裡可以看出來關聯關係呢?我們在上一篇文章中學過,多對一關聯的時候,外來鍵是存放在多方的,因此關聯關係我們要到comment表中去檢視,如下所示:
可以看到,兩條評論都已經成功儲存到comment表中了,並且這兩條評論的news_id都是2,說明它們是屬於第二條新聞的。怎麼樣,僅僅是在儲存資料之前建立好實體類之間的關係,再呼叫一下save()方法,那麼資料之間的關聯關係就會自動建立了,是不是非常簡單?上面的程式碼只是多對一情況的一種用法,還有一對一和多對多的情況,其實用法都是差不多的,相信你已經能舉一反三了。
另外,LitePal對集合資料的儲存還專門提供了一個方法,比如說我們有一個News集合,那麼應該怎樣去儲存這個集合中的每條News呢?傳統情況下可以這樣寫:
1 2 3 4 5 |
List<News> newsList; ... for (News news : newsList) { news.save(); } |
通過一個迴圈來遍歷出這個集合中的每一個News物件,然後逐個呼叫save()方法。這樣的寫法當然是可以的,但是效率會比較低,因為呼叫save()方法的時候除了會執行儲存操作之外,還會去分析News類的關聯關係,那麼每次迴圈都去重新分析一遍關聯關係顯然是比較耗時的。因此,LitePal提供了一個saveAll()方法,專門用於儲存集合資料的,用法如下所示:
1 2 3 |
List<News> newsList; ... DataSupport.saveAll(newsList); |
saveAll()方法接收一個Collection集合引數,只要把待儲存的集合資料傳入即可。這個方法可以完成和上面一段程式碼完全一樣的功能,但效率卻會高得多,而且寫法也更加簡單。
好了,這樣我們就把LitePal中提供的儲存操作的用法全部都學完了,那麼今天的文章就到這裡,下一篇文章當中會開始講解更新和刪除操作的用法。感興趣的朋友請繼續閱讀 Android資料庫高手祕籍(6):LitePal的修改和刪除操作 。