Android數據庫高手祕籍(七)——體驗LitePal的查詢藝術

前言

通過了多篇文章的學習,咱們已經把 LitePal 中的絕大部份內容都掌握了。如今回想起來了,增刪改查四種操做中的前三種咱們都已經學完了,不知道如今使用起數據庫來,你有沒有感受到格外的輕鬆和簡單。git

可是呢,咱們都知道,在全部的數據庫操做當中,查詢操做確定是最複雜的,用法也是最多的,所以 LitePal 在查詢方面提供的 API 也是比較豐富,並且 LitePal 在查詢方面的 API 設計也是頗爲藝術的。那麼今天咱們就專門使用一篇博客來說解一下查詢操做的用法,體驗一下 LitePal 查詢的藝術。尚未看過前面一篇文章的朋友建議先去參考 Android 數據庫高手祕籍 (六)——LitePal 的修改和刪除操做 。github

LitePal 的項目地址是:github.com/LitePalFram…sql

傳統的查詢數據方式

其實最傳統的查詢數據的方式固然是使用 SQL 語句了,Android 當中也提供了直接使用原生 SQL 語句來查詢數據庫表的方法,即 SQLiteDatabase 中的 rawQuery() 方法,方法定義以下:數據庫

public Cursor rawQuery(String sql, String[] selectionArgs)

複製代碼

其中,rawQuery() 方法接收兩個參數,第一個參數接收的就是一個 SQL 字符串,第二個參數是用於替換 SQL 語句中佔位符(?)的字符串數組。rawQuery() 方法返回一個 Cursor 對象,全部查詢到的數據都是封閉在這個對象當中的,咱們只要一一取出就能夠了。數組

固然這種用法其實並非很經常使用,由於相信大多數人都仍是不喜歡編寫 SQL 語句的。因此,Android 專門提供了一種封裝好的 API,使得咱們不用編寫 SQL 語句也能查詢出數據,即 SQLiteDatabase 中的 query() 方法。query() 提供了三個方法重載,其中參數最少的一個也有七個參數,咱們來看下方法定義:markdown

public Cursor query(String table, String[] columns, String selection,

            String[] selectionArgs, String groupBy, String having,
複製代碼

其中第一參數是表名,表示咱們但願從哪張表中查詢數據。第二個參數用於指定去查詢哪幾列,若是不指定則默認查詢全部列。第3、第四個參數用於去約束查詢某一行或某幾行的數據,不指定則默認是查詢全部行的數據。第五個參數用於指定須要去 group by 的列,不指定則表示不對查詢結果進行 group by 操做。第六個參數用於對 group by 以後的數據進行進一步的過濾,不指定則表示不進行過濾。第七個參數用於指定查詢結果的排序方式,不指定則表示使用默認的排序方式。app

這個方法是 query() 方法最少的一個方法重載了,另外還有兩個方法重載分別是八個和九個參數。雖然說這個方法在 Android 數據庫表查詢的時候很是經常使用,但重多的參數讓咱們在理解這個方法的時候可能會很費力,另外使用起來的時候也會至關的不爽。好比說,咱們想查詢 news 表中的全部數據,就應該要這樣寫:ide

SQLiteDatabase db = dbHelper.getWritableDatabase();

Cursor cursor = db.query("news", null, null, null, null, null, null);
複製代碼

能夠看到,將第一個表名參數指定成 news,而後後面的六個參數咱們都用不到,就所有指定成 null。函數

那若是是咱們想查詢 news 表中全部評論數大於零的新聞該怎麼寫呢?代碼以下所示:oop

SQLiteDatabase db = dbHelper.getWritableDatabase();

Cursor cursor = db.query("news", null, "commentcount>?", new String[]{"0"}, null, null, null);
複製代碼

因爲第三和第四個參數是用於指定約束條件的,因此咱們在第三個參數中指明瞭 commentcount>?,而後在第四個參數中經過一個 String 數組來替換佔位符,這樣查到的結果就是 news 表中全部評論數大於零的新聞了。那麼其它的幾個參數呢?仍然用不到,因此仍是隻能傳 null。

而後咱們能夠看到,query() 方法的返回值是一個 Cursor 對象,全部查詢到的數據都是封裝在這個對象中的,因此咱們還須要將數據逐一從 Cursor 對象中取出,而後設置到 News 實體類當中,以下所示:

List<News> newsList = new ArrayList<News>();

if (cursor != null && cursor.moveToFirst()) {

int id = cursor.getInt(cursor.getColumnIndex("id"));

		String title = cursor.getString(cursor.getColumnIndex("title"));

		String content = cursor.getString(cursor.getColumnIndex("content"));

		Date publishDate = new Date(cursor.getLong(cursor.getColumnIndex("publishdate")));

int commentCount = cursor.getInt(cursor.getColumnIndex("commentcount"));

		news.setContent(content);

		news.setPublishDate(publishDate);

		news.setCommentCount(commentCount);

	} while (cursor.moveToNext());
複製代碼

這大概就是傳統查詢數據方式的用法了,整體來看,用法確實很是不友好,尤爲是 query() 方法冗長的參數列表,即便咱們用不到那些參數,也必需要傳入許多個 null。另外,查詢到的數據還都只是封裝到了一個 Cursor 對象中,咱們還須要將數據一一取出而後再 set 到實體類對象當中。麻煩嗎?可能你以爲不麻煩,由於你已經習慣了這種用法。可是習慣老是能夠改變的,也許當你體驗了 LitePal 中查詢 API 給咱們帶來的便利以後,就會有了新的見解了,那麼下面咱們就一塊兒來體驗一下 LitePal 的查詢藝術。

使用 LitePal 查詢數據

LitePal 在查詢方面提供了很是豐富的 API,功能多種多樣,基本上已經可以知足咱們平時全部的查詢需求了。不只如此,LitePal 在查詢 API 的設計方面也是很是用心,摒棄了原生 query() 方法中繁瑣的參數列表,而是改用了一種更爲靈巧的方式——連綴查詢。除此以外,LitePal 查詢的結果也再也不返回 Cursor 對象,而後再由開發者本身去逐個取出,而是直接返回封裝好的對象。這些改變都使得查詢數據變得更加簡單,也更加合理,那麼下面咱們就來完整地學習一下 LitePal 中查詢數據的全部用法。

簡單查詢

好比說如今咱們想實現一個最簡單的功能,查詢 news 表中 id 爲 1 的這條記錄,使用 LitePal 就能夠這樣寫:

News news = DataSupport.find(News.class, 1);

複製代碼

天吶!有沒有以爲過輕鬆了?僅僅一行代碼,就能夠把 news 表中 id 爲 1 的記錄查出來了,並且結果仍是自動封裝到 News 對象裏的,也不須要咱們手動再從 Cursor 中去解析。若是是用原生的 SQL 語句,或者 query() 方法來寫,至少要 20 行左右的代碼才能完成一樣的功能!

那咱們先冷靜一下,來分析分析這個 find() 方法。能夠看到,它的參數列表也比較簡單,只接收兩個參數,第一個參數是一個泛型類,也就是說咱們在這裏指定什麼類,返回的對象就是什麼類,因此這裏傳入 News.class,那麼返回的對象也就是 News 了。第二個參數就更簡單了,就是一個 id 值,若是想要查詢 id 爲 1 的記錄就傳 1,想查 id 爲 2 的記錄就傳 2,以此類推。

原本一個還算頗爲複雜的功能,經過 LitePal 以後就變得這麼簡單了!那麼你可能已經火燒眉毛地想要學習更多 LitePal 中更多的查詢用法了,彆着急,咱們一個個來看。

你也許遇到過如下場景,在某些狀況下,你須要取出表中的第一條數據,那麼傳統的作法是怎麼樣的呢?在 SQL 語句中指定一個 limit 值,而後獲取返回結果的第一條記錄。可是在 LitePal 中不用這麼麻煩,好比咱們想要獲取 news 表中的第一條數據,只須要這樣寫:

News firstNews = DataSupport.findFirst(News.class);

複製代碼

OK,語義性很是強吧,讓人一眼就看懂是什麼意思了,只需調用 findFirst() 方法,而後傳入 News 類,獲得的就是 news 表中的第一條數據了。

那咱們舉一翻三,若是是想要獲取 News 表中的最後一條數據該怎麼寫呢?一樣簡單,以下所示:

News lastNews = DataSupport.findLast(News.class);

複製代碼

由於獲取表中第一條或者是最後一條數據的場景比較常見,因此 LitePal 特地提供了這兩個方法來方便咱們的操做。

那麼咱們看到這裏,目前都只是查詢單條數據的功能,若是想要查詢多條數據該怎麼辦呢?好比說,咱們想把 news 表中 id 爲 一、三、五、7 的數據都查出來,該怎麼寫呢?也許有的朋友會比較聰明,立馬就想到能夠一個個去查,就調用四次 find() 方法嘛,而後把 一、三、五、7 這四個 id 分別傳進去不就能夠了。沒錯,這樣作徹底是能夠的,並且效率也並不低,可是 LitePal 給咱們提供了一個更簡便的方法——findAll()。這個方法的用法和 find() 方法是很是相似的,只不過它能夠指定多個 id,而且返回值也再也不是一個泛型類對象,而是一個泛型類集合,以下所示:

List<News> newsList = DataSupport.findAll(News.class, 1, 3, 5, 7);

複製代碼

能夠看到,首先咱們是調用的 findAll() 方法,而後這個方法的第一個參數仍然是指定的泛型類,可是後面的參數就很隨意了,你能夠傳入任意個 id 進去,findAll() 方法會把全部傳入的 id 所對應的數據所有查出來,而後一塊兒返回到 List 這個泛型集合當中。

雖然說這個語法設計算是至關人性化,可是在有些場景或許不太適用,由於可能要你要查詢的多個 id 已經封裝到一個數組裏了。那麼不要緊,findAll() 方法也是接收數組參數的,因此說一樣的功能你也能夠這樣寫:

long[] ids = new long[] { 1, 3, 5, 7 };

List<News> newsList = DataSupport.findAll(News.class, ids);
複製代碼

看到這裏,那有的朋友可能會奇怪了,說 findAll() 方法不該該是查詢全部數據的意思嗎?怎麼老是查詢幾個 id 所對應數據呢?哈!這個問題問得好,由於 findAll() 方法也是能夠查詢全部數據的,並且查詢全部數據的寫法更簡單,只須要這樣寫:

List<News> allNews = DataSupport.findAll(News.class);

複製代碼

看到沒有,咱們只須要把後面的參數都去掉,在不指定具體 id 的狀況下,findAll() 方法查詢出的就是 news 表中的全部數據了,是否是語義性很是強?

並且你們不要覺得剛纔這些都只是 findAll() 的幾個方法重載而已,實際上剛纔咱們的這幾種用法都是調用的同一個 findAll() 方法!一個方法卻可以實現多種不一樣的查詢效果,而且語義性也很強,讓人一看就能理解,這就是 LitePal 的查詢藝術!

連綴查詢

固然了,LitePal 給咱們提供的查詢功能還遠遠不僅這些,好戲還在後頭。相信你們如今也已經發現了,咱們目前的查詢功能都是基於 id 來進行查詢的,並不能隨意地指定查詢條件。那麼怎樣才能指定查詢條件呢?讓咱們回想一下傳統狀況應該怎麼作,query() 方法中接收七個參數,其中第三和第四個參數就是用於指定查詢條件的,而後其它幾個參數都填 null 就能夠了。可是呢,前面咱們已經痛批過了這種寫法,由於冗長的參數列表太過繁瑣,那麼 LitePal 又是怎麼解決這個問題的呢?咱們如今就來學習一下。

爲了不冗長的參數列表,LitePal 採用了一種很是巧妙的解決方案,叫做連綴查詢,這種查詢很靈活,能夠根據咱們實際的查詢需求來動態配置查詢參數。 那這裏舉個簡單的例子,好比咱們想查詢 news 表中全部評論數大於零的新聞,就能夠這樣寫:

List<News> newsList = DataSupport.where("commentcount > ?", "0").find(News.class);

複製代碼

能夠看到,首先是調用了 DataSupport 的 where() 方法,在這裏指定了查詢條件。where() 方法接收任意個字符串參數,其中第一個參數用於進行條件約束,從第二個參數開始,都是用於替換第一個參數中的佔位符的。那這個 where() 方法就對應了一條 SQL 語句中的 where 部分。

接着咱們在 where() 方法以後直接連綴了一個 find() 方法,而後在這裏指定一個泛型類,表示用於查詢哪張表。那麼上面的一段代碼,查詢出的結果和以下 SQL 語句是相同的:

select * from users where commentcount > 0;

複製代碼

可是這樣會將 news 表中全部的列都查詢出來,也許你並不須要那麼多的數據,而是隻要 title 和 content 這兩列數據。那麼也很簡單,咱們只要再增長一個連綴就好了,以下所示:

List<News> newsList = DataSupport.select("title", "content")

		.where("commentcount > ?", "0").find(News.class);
複製代碼

能夠看到,這裏咱們新增了一個 select() 方法,這個方法接收任意個字符串參數,每一個參數要求對應一個列名,這樣就只會把相應列的數據查詢出來了,所以 select() 方法對應了一條 SQL 語句中的 select 部分。

那麼上面的一段代碼,查詢出的結果和以下 SQL 語句是相同的:

select title,content from users where commentcount > 0;

複製代碼

很好玩吧?不過這還不算完呢,咱們還能夠繼續連綴更多的東西。好比說,我但願將查詢出的新聞按照發布的時間倒序排列,即最新發布的新聞放在最前面,那就能夠這樣寫:

List<News> newsList = DataSupport.select("title", "content")

		.where("commentcount > ?", "0")

		.order("publishdate desc").find(News.class);
複製代碼

order() 方法中接收一個字符串參數,用於指定查詢出的結果按照哪一列進行排序,asc 表示正序排序,desc 表示倒序排序,所以 order() 方法對應了一條 SQL 語句中的 order by 部分。

那麼上面的一段代碼,查詢出的結果和以下 SQL 語句是相同的:

select title,content from users where commentcount > 0 order by publishdate desc;

複製代碼

而後呢,也許你並不但願將全部條件匹配的結果一次性所有查詢出來,由於這樣數據量可能會有點太大了,而是但願只查詢出前 10 條數據,那麼使用連綴一樣能夠輕鬆解決這個問題,代碼以下所示:

List<News> newsList = DataSupport.select("title", "content")

		.where("commentcount > ?", "0")

		.order("publishdate desc").limit(10).find(News.class);
複製代碼

這裏咱們又連綴了一個 limit() 方法,這個方法接收一個整型參數,用於指定查詢前幾條數據,這裏指定成 10,意思就是查詢全部匹配結果中的前 10 條數據。

那麼上面的一段代碼,查詢出的結果和以下 SQL 語句是相同的:

select title,content from users where commentcount > 0 order by publishdate desc limit 10;

複製代碼

剛纔咱們查詢到的是全部匹配條件的前 10 條新聞,那麼如今我想對新聞進行分頁展現,翻到第二頁時,展現第 11 到第 20 條新聞,這又該怎麼實現呢?不要緊,在 LitePal 的幫助下,這些功能都是十分簡單的,只須要再連綴一個偏移量就能夠了,以下所示:

List<News> newsList = DataSupport.select("title", "content")

		.where("commentcount > ?", "0")

		.order("publishdate desc").limit(10).offset(10)
複製代碼

能夠看到,這裏咱們又添加了一個 offset() 方法,用於指定查詢結果的偏移量,這裏指定成 10,就表示偏移十個位置,那麼原來是查詢前 10 條新聞的,偏移了十個位置以後,就變成了查詢第 11 到第 20 條新聞了,若是偏移量是 20,那就表示查詢第 21 到第 30 條新聞,以此類推。所以,limit() 方法和 offset() 方法共同對應了一條 SQL 語句中的 limit 部分。

那麼上面的一段代碼,查詢出的結果和以下 SQL 語句是相同的:

select title,content from users where commentcount > 0 order by publishdate desc limit 10,10;

複製代碼

這大概就是 LitePal 中連綴查詢的全部用法了。看出區別了吧?這種查詢的好處就在於,咱們能夠隨意地組合各類查詢參數,須要用到的時候就把它們連綴到一塊兒,不須要用到的時候不用指定就能夠了。對比一下 query() 方法中那冗長的參數列表,即便咱們用不到那些參數,也必需要傳 null,是否是明顯感受 LitePal 中的查詢更加人性化?

激進查詢

不過,上述咱們的全部用法中,都只能是查詢到指定表中的數據而已,關聯表中數據是沒法查到的,由於 LitePal 默認的模式就是懶查詢,固然這也是推薦的查詢方式。那麼,若是你真的很是想要一次性將關聯表中的數據也一塊兒查詢出來,固然也是能夠的,LitePal 中也支持激進查詢的方式,下面咱們就來一塊兒看一下。

不知道你有沒有發現,剛纔咱們所學的每個類型的 find() 方法,都對應了一個帶有 isEager 參數的方法重載,這個參數相信你們一看就明白是什麼意思了,設置成 true 就表示激進查詢,這樣就會把關聯表中的數據一塊兒查詢出來了。

好比說,咱們想要查詢 news 表中 id 爲 1 的新聞,而且把這條新聞所對應的評論也一塊兒查詢出來,就能夠這樣寫:

News news = DataSupport.find(News.class, 1, true);

List<Comment> commentList = news.getCommentList();
複製代碼

能夠看到,這裏並無什麼複雜的用法,也就是在 find() 方法的最後多加了一個 true 參數,就表示使用激進查詢了。這會將和 news 表關聯的全部表中的數據也一塊兒查出來,那麼 comment 表和 news 表是多對一的關聯,因此使用激進查詢一條新聞的時候,那麼該新聞所對應的評論也就一塊兒被查詢出來了。

激進查詢的用法很是簡單,就只有這麼多,其它 find() 方法也都是一樣的用法,就再也不重複介紹了。可是這種查詢方式 LitePal 並不推薦,由於若是一旦關聯表中的數據不少,查詢速度可能就會很是慢。並且激進查詢只能查詢出指定表的關聯表數據,可是無法繼續迭代查詢關聯表的關聯表數據。所以,這裏我建議你們仍是使用默認的懶加載更加合適,至於如何查詢出關聯表中的數據,其實只須要在模型類中作一點小修改就能夠了。修改 News 類中的代碼,以下所示:

public class News extends DataSupport{

public List<Comment> getComments() {

return DataSupport.where("news_id = ?", String.valueOf(id)).find(Comment.class);
複製代碼

能夠看到,咱們在 News 類中添加了一個 getComments() 方法,而這個方法的內部就是使用了一句連綴查詢,查出了當前這條新聞對應的全部評論。改爲這種寫法以後,咱們就能夠將關聯表數據的查詢延遲,當咱們須要去獲取新聞所對應的評論時,再去調用 News 的 getComments() 方法,這時纔會去查詢關聯數據。這種寫法會比激進查詢更加高效也更加合理。

原生查詢

相信你已經體會到,LitePal 在查詢方面提供的 API 已經至關豐富了。可是,也許你總會遇到一些千奇百怪的需求,可能使用 LitePal 提供的查詢 API 沒法完成這些需求。沒有關係,由於即便使用了 LitePal,你仍然可使用原生的查詢方式 (SQL 語句) 來去查詢數據。DataSuppport 類中還提供了一個 findBySQL()方法,使用這個方法就能經過原生的 SQL 語句方式來查詢數據了,以下所示:

Cursor cursor = DataSupport.findBySQL("select * from news where commentcount>?", "0");

複製代碼

findBySQL() 方法接收任意個字符串參數,其中第一個參數就是 SQL 語句,後面的參數都是用於替換 SQL 語句中的佔位符的,用法很是簡單。另外,findBySQL() 方法返回的是一個 Cursor 對象,這和原生 SQL 語句的用法返回的結果也是相同的。

好了,這樣咱們就把 LitePal 中提供的查詢數據的方法所有都學完了,那麼今天的文章就到這裏,下一篇文章當中會開始講解聚合函數的用法,感興趣的朋友請繼續閱讀。

相關文章
相關標籤/搜索