經過了多篇文章的學習,我們已經把LitePal中的絕大部分內容都掌握了。現在回想起來了,增刪改查四種操作中的前三種我們都已經學完了,不知道現在使用起資料庫來,你有沒有感覺到格外的輕鬆和簡單。但是呢,我們都知道,在所有的資料庫操作當中,查詢操作肯定是最複雜的,用法也是最多的,因此LitePal在查詢方面提供的API也是比較豐富,而且LitePal在查詢方面的API設計也是頗為藝術的。那麼今天我們就專門使用一篇部落格來講解一下查詢操作的用法,體驗一下LitePal查詢的藝術。還沒有看過前面一篇文章的朋友建議先去參考 Android資料庫高手祕籍(6):LitePal的修改和刪除操作 。
LitePal的專案地址是:https://github.com/LitePalFramework/LitePal
傳統的查詢資料方式
其實最傳統的查詢資料的方式當然是使用SQL語句了,Android當中也提供了直接使用原生SQL語句來查詢資料庫表的方法,即SQLiteDatabase中的rawQuery()方法,方法定義如下:
1 |
public Cursor rawQuery(String sql, String[] selectionArgs) |
其中,rawQuery()方法接收兩個引數,第一個引數接收的就是一個SQL字串,第二個引數是用於替換SQL語句中佔位符(?)的字串陣列。rawQuery()方法返回一個Cursor物件,所有查詢到的資料都是封閉在這個物件當中的,我們只要一一取出就可以了。
當然這種用法其實並不是很常用,因為相信大多數人都還是不喜歡編寫SQL語句的。所以,Android專門提供了一種封裝好的API,使得我們不用編寫SQL語句也能查詢出資料,即SQLiteDatabase中的query()方法。query()提供了三個方法過載,其中引數最少的一個也有七個引數,我們來看下方法定義:
1 2 3 |
public Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) |
其中第一引數是表名,表示我們希望從哪張表中查詢資料。第二個引數用於指定去查詢哪幾列,如果不指定則預設查詢所有列。第三、第四個引數用於去約束查詢某一行或某幾行的資料,不指定則預設是查詢所有行的資料。第五個引數用於指定需要去group by的列,不指定則表示不對查詢結果進行group by操作。第六個引數用於對group by之後的資料進行進一步的過濾,不指定則表示不進行過濾。第七個引數用於指定查詢結果的排序方式,不指定則表示使用預設的排序方式。
這個方法是query()方法最少的一個方法過載了,另外還有兩個方法過載分別是八個和九個引數。雖說這個方法在Android資料庫表查詢的時候非常常用,但重多的引數讓我們在理解這個方法的時候可能會很費力,另外使用起來的時候也會相當的不爽。比如說,我們想查詢news表中的所有資料,就應該要這樣寫:
1 2 |
SQLiteDatabase db = dbHelper.getWritableDatabase(); Cursor cursor = db.query("news", null, null, null, null, null, null); |
可以看到,將第一個表名引數指定成news,然後後面的六個引數我們都用不到,就全部指定成null。
那如果是我們想查詢news表中所有評論數大於零的新聞該怎麼寫呢?程式碼如下所示:
1 2 |
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實體類當中,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
List<News> newsList = new ArrayList<News>(); if (cursor != null && cursor.moveToFirst()) { do { 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 news = new News(); news.setId(id); news.setTitle(title); news.setContent(content); news.setPublishDate(publishDate); news.setCommentCount(commentCount); newsList.add(news); } while (cursor.moveToNext()); } |
這大概就是傳統查詢資料方式的用法了,總體來看,用法確實非常不友好,尤其是query()方法冗長的引數列表,即使我們用不到那些引數,也必須要傳入許多個null。另外,查詢到的資料還都只是封裝到了一個Cursor物件中,我們還需要將資料一一取出然後再set到實體類物件當中。麻煩嗎?可能你覺得不麻煩,因為你已經習慣了這種用法。但是習慣總是可以改變的,也許當你體驗了LitePal中查詢API給我們帶來的便利之後,就會有了新的看法了,那麼下面我們就一起來體驗一下LitePal的查詢藝術。
使用LitePal查詢資料
LitePal在查詢方面提供了非常豐富的API,功能多種多樣,基本上已經能夠滿足我們平時所有的查詢需求了。不僅如此,LitePal在查詢API的設計方面也是非常用心,摒棄了原生query()方法中繁瑣的引數列表,而是改用了一種更為靈巧的方式——連綴查詢。除此之外,LitePal查詢的結果也不再返回Cursor物件,然後再由開發者自己去逐個取出,而是直接返回封裝好的物件。這些改變都使得查詢資料變得更加簡單,也更加合理,那麼下面我們就來完整地學習一下LitePal中查詢資料的所有用法。
簡單查詢
比如說現在我們想實現一個最簡單的功能,查詢news表中id為1的這條記錄,使用LitePal就可以這樣寫:
1 |
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表中的第一條資料,只需要這樣寫:
1 |
News firstNews = DataSupport.findFirst(News.class); |
OK,語義性非常強吧,讓人一眼就看懂是什麼意思了,只需呼叫findFirst()方法,然後傳入News類,得到的就是news表中的第一條資料了。
那我們舉一翻三,如果是想要獲取News表中的最後一條資料該怎麼寫呢?同樣簡單,如下所示:
1 |
News lastNews = DataSupport.findLast(News.class); |
因為獲取表中第一條或者是最後一條資料的場景比較常見,所以LitePal特意提供了這兩個方法來方便我們的操作。
那麼我們看到這裡,目前都只是查詢單條資料的功能,如果想要查詢多條資料該怎麼辦呢?比如說,我們想把news表中id為1、3、5、7的資料都查出來,該怎麼寫呢?也許有的朋友會比較聰明,立馬就想到可以一個個去查,就呼叫四次find()方法嘛,然後把1、3、5、7這四個id分別傳進去不就可以了。沒錯,這樣做完全是可以的,而且效率也並不低,但是LitePal給我們提供了一個更簡便的方法——findAll()。這個方法的用法和find()方法是非常類似的,只不過它可以指定多個id,並且返回值也不再是一個泛型類物件,而是一個泛型類集合,如下所示:
1 |
List<News> newsList = DataSupport.findAll(News.class, 1, 3, 5, 7); |
可以看到,首先我們是呼叫的findAll()方法,然後這個方法的第一個引數仍然是指定的泛型類,但是後面的引數就很隨意了,你可以傳入任意個id進去,findAll()方法會把所有傳入的id所對應的資料全部查出來,然後一起返回到List<News>這個泛型集合當中。
雖說這個語法設計算是相當人性化,但是在有些場景或許不太適用,因為可能要你要查詢的多個id已經封裝到一個陣列裡了。那麼沒關係,findAll()方法也是接收陣列引數的,所以說同樣的功能你也可以這樣寫:
1 2 |
long[] ids = new long[] { 1, 3, 5, 7 }; List<News> newsList = DataSupport.findAll(News.class, ids); |
看到這裡,那有的朋友可能會奇怪了,說findAll()方法不應該是查詢所有資料的意思嗎?怎麼總是查詢幾個id所對應資料呢?哈!這個問題問得好,因為findAll()方法也是可以查詢所有資料的,而且查詢所有資料的寫法更簡單,只需要這樣寫:
1 |
List<News> allNews = DataSupport.findAll(News.class); |
看到沒有,我們只需要把後面的引數都去掉,在不指定具體id的情況下,findAll()方法查詢出的就是news表中的所有資料了,是不是語義性非常強?
而且大家不要以為剛才這些都只是findAll()的幾個方法過載而已,實際上剛才我們的這幾種用法都是呼叫的同一個findAll()方法!一個方法卻能夠實現多種不同的查詢效果,並且語義性也很強,讓人一看就能理解,這就是LitePal的查詢藝術!
連綴查詢
當然了,LitePal給我們提供的查詢功能還遠遠不只這些,好戲還在後頭。相信大家現在也已經發現了,我們目前的查詢功能都是基於id來進行查詢的,並不能隨意地指定查詢條件。那麼怎樣才能指定查詢條件呢?讓我們回想一下傳統情況應該怎麼做,query()方法中接收七個引數,其中第三和第四個引數就是用於指定查詢條件的,然後其它幾個引數都填null就可以了。但是呢,前面我們已經痛批過了這種寫法,因為冗長的引數列表太過繁瑣,那麼LitePal又是怎麼解決這個問題的呢?我們現在就來學習一下。
為了避免冗長的引數列表,LitePal採用了一種非常巧妙的解決方案,叫作連綴查詢,這種查詢很靈活,可以根據我們實際的查詢需求來動態配置查詢引數。 那這裡舉個簡單的例子,比如我們想查詢news表中所有評論數大於零的新聞,就可以這樣寫:
1 |
List<News> newsList = DataSupport.where("commentcount > ?", "0").find(News.class); |
可以看到,首先是呼叫了DataSupport的where()方法,在這裡指定了查詢條件。where()方法接收任意個字串引數,其中第一個引數用於進行條件約束,從第二個引數開始,都是用於替換第一個引數中的佔位符的。那這個where()方法就對應了一條SQL語句中的where部分。
接著我們在where()方法之後直接連綴了一個find()方法,然後在這裡指定一個泛型類,表示用於查詢哪張表。那麼上面的一段程式碼,查詢出的結果和如下SQL語句是相同的:
1 |
select * from users where commentcount > 0; |
但是這樣會將news表中所有的列都查詢出來,也許你並不需要那麼多的資料,而是隻要title和content這兩列資料。那麼也很簡單,我們只要再增加一個連綴就行了,如下所示:
1 2 |
List<News> newsList = DataSupport.select("title", "content") .where("commentcount > ?", "0").find(News.class); |
可以看到,這裡我們新增了一個select()方法,這個方法接收任意個字串引數,每個引數要求對應一個列名,這樣就只會把相應列的資料查詢出來了,因此select()方法對應了一條SQL語句中的select部分。
那麼上面的一段程式碼,查詢出的結果和如下SQL語句是相同的:
1 |
select title,content from users where commentcount > 0; |
很好玩吧?不過這還不算完呢,我們還可以繼續連綴更多的東西。比如說,我希望將查詢出的新聞按照發布的時間倒序排列,即最新發布的新聞放在最前面,那就可以這樣寫:
1 2 3 |
List<News> newsList = DataSupport.select("title", "content") .where("commentcount > ?", "0") .order("publishdate desc").find(News.class); |
order()方法中接收一個字串引數,用於指定查詢出的結果按照哪一列進行排序,asc表示正序排序,desc表示倒序排序,因此order()方法對應了一條SQL語句中的order by部分。
那麼上面的一段程式碼,查詢出的結果和如下SQL語句是相同的:
1 |
select title,content from users where commentcount > 0 order by publishdate desc; |
然後呢,也許你並不希望將所有條件匹配的結果一次性全部查詢出來,因為這樣資料量可能會有點太大了,而是希望只查詢出前10條資料,那麼使用連綴同樣可以輕鬆解決這個問題,程式碼如下所示:
1 2 3 |
List<News> newsList = DataSupport.select("title", "content") .where("commentcount > ?", "0") .order("publishdate desc").limit(10).find(News.class); |
這裡我們又連綴了一個limit()方法,這個方法接收一個整型引數,用於指定查詢前幾條資料,這裡指定成10,意思就是查詢所有匹配結果中的前10條資料。
那麼上面的一段程式碼,查詢出的結果和如下SQL語句是相同的:
1 |
select title,content from users where commentcount > 0 order by publishdate desc limit 10; |
剛才我們查詢到的是所有匹配條件的前10條新聞,那麼現在我想對新聞進行分頁展示,翻到第二頁時,展示第11到第20條新聞,這又該怎麼實現呢?沒關係,在LitePal的幫助下,這些功能都是十分簡單的,只需要再連綴一個偏移量就可以了,如下所示:
1 2 3 4 |
List<News> newsList = DataSupport.select("title", "content") .where("commentcount > ?", "0") .order("publishdate desc").limit(10).offset(10) .find(News.class); |
可以看到,這裡我們又新增了一個offset()方法,用於指定查詢結果的偏移量,這裡指定成10,就表示偏移十個位置,那麼原來是查詢前10條新聞的,偏移了十個位置之後,就變成了查詢第11到第20條新聞了,如果偏移量是20,那就表示查詢第21到第30條新聞,以此類推。因此,limit()方法和order()方法共同對應了一條SQL語句中的limit部分。
那麼上面的一段程式碼,查詢出的結果和如下SQL語句是相同的:
1 |
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的新聞,並且把這條新聞所對應的評論也一起查詢出來,就可以這樣寫:
1 2 |
News news = DataSupport.find(News.class, 1, true); List<Comment> commentList = news.getCommentList(); |
可以看到,這裡並沒有什麼複雜的用法,也就是在find()方法的最後多加了一個true引數,就表示使用激進查詢了。這會將和news表關聯的所有表中的資料也一起查出來,那麼comment表和news表是多對一的關聯,所以使用激進查詢一條新聞的時候,那麼該新聞所對應的評論也就一起被查詢出來了。
激進查詢的用法非常簡單,就只有這麼多,其它find()方法也都是同樣的用法,就不再重複介紹了。但是這種查詢方式LitePal並不推薦,因為如果一旦關聯表中的資料很多,查詢速度可能就會非常慢。而且激進查詢只能查詢出指定表的關聯表資料,但是沒法繼續迭代查詢關聯表的關聯表資料。因此,這裡我建議大家還是使用預設的懶載入更加合適,至於如何查詢出關聯表中的資料,其實只需要在模型類中做一點小修改就可以了。修改News類中的程式碼,如下所示:
1 2 3 4 5 6 7 8 9 |
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語句方式來查詢資料了,如下所示:
1 |
Cursor cursor = DataSupport.findBySQL("select * from news where commentcount>?", "0"); |
findBySQL()方法接收任意個字串引數,其中第一個引數就是SQL語句,後面的引數都是用於替換SQL語句中的佔位符的,用法非常簡單。另外,findBySQL()方法返回的是一個Cursor物件,這和原生SQL語句的用法返回的結果也是相同的。
好了,這樣我們就把LitePal中提供的查詢資料的方法全部都學完了,那麼今天的文章就到這裡,下一篇文章當中會開始講解聚合函式的用法,感興趣的朋友請繼續閱讀 Android資料庫高手祕籍(8):使用LitePal的聚合函式 。