現(xiàn)在回想起來,增刪改查四種操作中的前三種我們都已經(jīng)學(xué)完了,有沒有感覺到格外的輕松和簡單。LitePal在查詢方面提供的API也是比較豐富,而且LitePal在查詢方面的API設(shè)計也是頗為藝術(shù)的。還沒有看過前面一篇文章的朋友建議先去參考 Android數(shù)據(jù)庫高手秘籍(六)LitePal的修改和刪除操作
LitePal的項目地址是:https://github.com/LitePalFramework/LitePal
傳統(tǒng)的查詢數(shù)據(jù)方式
最傳統(tǒng)的查詢數(shù)據(jù)的方式當(dāng)然是使用SQL語句了,Android當(dāng)中也提供了直接使用原生SQL語句來查詢數(shù)據(jù)庫表的方法,即SQLiteDatabase中的rawQuery()方法,方法定義如下:
public Cursor rawQuery(String sql, String[] selectionArgs)
其中,rawQuery()方法接收兩個參數(shù),第一個參數(shù)接收的就是一個SQL字符串,第二個參數(shù)是用于替換SQL語句中占位符(?)的字符串?dāng)?shù)組。rawQuery()方法返回一個Cursor對象,所有查詢到的數(shù)據(jù)都是封閉在這個對象當(dāng)中的,我們只要一 一取出就可以了。
相信大多數(shù)人都還是不喜歡編寫SQL語句的。所以,Android專門提供了一種封裝好的API,使得我們不用編寫SQL語句也能查詢出數(shù)據(jù),即SQLiteDatabase中的query()方法。query()提供了三個方法重載,其中參數(shù)最少的一個也有七個參數(shù),我們來看下方法定義:
public Cursor query(String table, String[] columns, String selection,
String[] selectionArgs, String groupBy, String having,
String orderBy)
其中第一參數(shù)是表名,表示我們希望從哪張表中查詢數(shù)據(jù)。第二個參數(shù)用于指定去查詢哪幾列,如果不指定則默認(rèn)查詢所有列。第三、第四個參數(shù)用于去約束查詢某一行或某幾行的數(shù)據(jù),不指定則默認(rèn)是查詢所有行的數(shù)據(jù)。第五個參數(shù)用于指定需要去group by的列,不指定則表示不對查詢結(jié)果進行g(shù)roup by操作。第六個參數(shù)用于對group by之后的數(shù)據(jù)進行進一步的過濾,不指定則表示不進行過濾。第七個參數(shù)用于指定查詢結(jié)果的排序方式,不指定則表示使用默認(rèn)的排序方式。
這個方法是query()方法最少的一個方法重載了,另外還有兩個方法重載分別是八個和九個參數(shù)。雖說這個方法在Android數(shù)據(jù)庫表查詢的時候非常常用,但重多的參數(shù)讓我們在理解這個方法的時候可能會很費力,另外使用起來的時候也會相當(dāng)?shù)牟凰1热缯f,我們想查詢news表中的所有數(shù)據(jù),就應(yīng)該要這樣寫:
SQLiteDatabase db = dbHelper.getWritableDatabase();
Cursor cursor = db.query("news", null, null, null, null, null, null);
可以看到,將第一個表名參數(shù)指定成news,然后后面的六個參數(shù)我們都用不到,就全部指定成null。
那如果是我們想查詢news表中所有評論數(shù)大于零的新聞該怎么寫呢?代碼如下所示:
SQLiteDatabase db = dbHelper.getWritableDatabase();
Cursor cursor = db.query("news", null, "commentcount>?", new String[]{"0"}, null, null, null);
由于第三和第四個參數(shù)是用于指定約束條件的,所以我們在第三個參數(shù)中指明了commentcount>?,然后在第四個參數(shù)中通過一個String數(shù)組來替換占位符,這樣查到的結(jié)果就是news表中所有評論數(shù)大于零的新聞了。那么其它的幾個參數(shù)呢?仍然用不到,所以還是只能傳null。
然后我們可以看到,query()方法的返回值是一個Cursor對象,所有查詢到的數(shù)據(jù)都是封裝在這個對象中的,所以我們還需要將數(shù)據(jù)逐一從Cursor對象中取出,然后設(shè)置到News實體類當(dāng)中,如下所示:
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")));
News news = new News();
news.setId(id);
news.setTitle(title);
news.setContent(content);
news.setPublishDate(publishDate);
newsList.add(news);
} while (cursor.moveToNext());
}
這是傳統(tǒng)查詢數(shù)據(jù)方式的用法了,總體來看,用法確實非常不友好,尤其是query()方法冗長的參數(shù)列表,即使我們用不到那些參數(shù),也必須要傳入許多個null。另外,查詢到的數(shù)據(jù)還都只是封裝到了一個Cursor對象中,我們還需要將數(shù)據(jù)一 一取出然后再set到實體類對象當(dāng)中。麻煩嗎?可能你覺得不麻煩,因為你已經(jīng)習(xí)慣了這種用法。但是習(xí)慣總是可以改變的,也許當(dāng)你體驗了LitePal中查詢API給我們帶來的便利之后,就會有了新的看法了,那么下面我們就一起來體驗一下LitePal的查詢藝術(shù)。
使用LitePal查詢數(shù)據(jù)
LitePal在查詢方面提供了非常豐富的API,基本上已經(jīng)能夠滿足我們平時所有的查詢需求了。不僅如此,LitePal在查詢API的設(shè)計方面也是非常用心,摒棄了原生query()方法中繁瑣的參數(shù)列表,而是改用了一種更為靈巧的方式 連綴查詢。除此之外,LitePal查詢的結(jié)果也不再返回Cursor對象,而是直接返回封裝好的對象。這些改變都使得查詢數(shù)據(jù)變得更加簡單,也更加合理,那么下面我們就來完整地學(xué)習(xí)一下LitePal中查詢數(shù)據(jù)的所有用法。
簡單查詢
比如說查詢news表中id為1的這條記錄,使用LitePal就可以這樣寫:
News news = LitePal.find(News.class, 1);
僅僅一行代碼,就可以把news表中id為1的記錄查出來了,而且結(jié)果還是自動封裝到News對象里的,也不需要我們手動再從Cursor中去解析。如果是用原生的SQL語句,或者query()方法來寫,要寫一大推!
可以看到,它的參數(shù)列表也比較簡單,只接收兩個參數(shù),這里傳入News.class,那么返回的對象也就是News了。第二個參數(shù)就更簡單了,就是一個id值,如果想要查詢id為1的記錄就傳1,想查id為2的記錄就傳2,以此類推。
你也許遇到過以下場景,在某些情況下,你需要取出表中的第一條數(shù)據(jù),那么傳統(tǒng)的做法是怎么樣的呢?在SQL語句中指定一個limit值,然后獲取返回結(jié)果的第一條記錄。但是在LitePal中不用這么麻煩,比如我們想要獲取news表中的第一條數(shù)據(jù),只需要這樣寫:
News first = LitePal.findFirst(News.class);
只需調(diào)用findFirst()方法,然后傳入News類,得到的就是news表中的第一條數(shù)據(jù)了。
那我們舉一反三,如果是想要獲取News表中的最后一條數(shù)據(jù)該怎么寫呢?同樣簡單,如下所示:
News last = LitePal.findLast(News.class);
獲取表中第一條或者是最后一條數(shù)據(jù)的場景比較常見,所以LitePal特意提供了這兩個方法來方便我們的操作。
目前都只是查詢單條數(shù)據(jù)的功能,如果想要查詢多條數(shù)據(jù)該怎么辦呢?比如說,我們想把news表中id為1、3、5、7的數(shù)據(jù)都查出來,該怎么寫呢?LitePal給我們提供了一個更簡便的方法——findAll()。這個方法的用法和find()方法是非常類似的,只不過它可以指定多個id,并且返回值也不再是一個泛型類對象,而是一個泛型類集合,如下所示:
List<News> all = LitePal.findAll(News.class, 1, 3, 5, 7);
可以看到,首先我們是調(diào)用的findAll()方法,然后這個方法的第一個參數(shù)仍然是指定的泛型類,但是后面的參數(shù)就很隨意了,你可以傳入任意個id進去,findAll()方法會把所有傳入的id所對應(yīng)的數(shù)據(jù)全部查出來,然后一起返回到List<News>這個泛型集合當(dāng)中。
findAll()方法也是可以查詢所有數(shù)據(jù)的,而且查詢所有數(shù)據(jù)的寫法更簡單,只需要這樣寫:
List<News> all = LitePal.findAll(News.class);
我們只需要把后面的參數(shù)都去掉,在不指定具體id的情況下,findAll()方法查詢出的就是news表中的所有數(shù)據(jù)了,是不是語義性非常強?
而且大家不要以為剛才這些都只是findAll()的幾個方法重載而已,實際上剛才我們的這幾種用法都是調(diào)用的同一個findAll()方法!一個方法卻能夠?qū)崿F(xiàn)多種不同的查詢效果,并且語義性也很強,讓人一看就能理解,這就是LitePal的查詢藝術(shù)!
連綴查詢
LitePal給我們提供的查詢功能還遠遠不只這些,不能隨意地指定查詢條件。讓我們回想一下傳統(tǒng)情況應(yīng)該怎么做,query()方法中接收七個參數(shù),其中第三和第四個參數(shù)就是用于指定查詢條件的,然后其它幾個參數(shù)都填null就可以了。但是呢,前面我們已經(jīng)痛批過了這種寫法,因為冗長的參數(shù)列表太過繁瑣,那么LitePal又是怎么解決這個問題的呢?我們現(xiàn)在就來學(xué)習(xí)一下。
為了避免冗長的參數(shù)列表,LitePal采用了一種非常巧妙的解決方案,叫作連綴查詢,這種查詢很靈活,可以根據(jù)我們實際的查詢需求來動態(tài)配置查詢參數(shù)。 那這里舉個簡單的例子,比如我們想查詢news表中所有標(biāo)題等于Android智能手機的 新聞,就可以這樣寫:
List<News> newsList = LitePal.where("title = 'Android智能手機' ").find(News.class);
可以看到,首先是調(diào)用了LitePal的where()方法,在這里指定了查詢條件。where()方法接收任意個字符串參數(shù),其中第一個參數(shù)用于進行條件約束,從第二個參數(shù)開始,都是用于替換第一個參數(shù)中的占位符的。那這個where()方法就對應(yīng)了一條SQL語句中的where部分。
接著我們在where()方法之后直接連綴了一個find()方法,然后在這里指定一個泛型類,表示用于查詢哪張表。那么上面的一段代碼,查詢出的結(jié)果和如下SQL語句是相同的:
select * from news where title = "'Android智能手機";
但是這樣會將news表中所有的列都查詢出來,也許你并不需要那么多的數(shù)據(jù),而是只要title和content這兩列數(shù)據(jù)。那么也很簡單,我們只要再增加一個連綴就行了,如下所示:
List<News> newsList = LitePal.select("title", "content")
.where("title = ? ", "Android智能手機").find(News.class);
for (News news1 : newsList) {
Log.d(TAG, news1.toString());
}
可以看到,這里我們新增了一個select()方法,這個方法接收任意個字符串參數(shù),每個參數(shù)要求對應(yīng)一個列名,這樣就只會把相應(yīng)列的數(shù)據(jù)查詢出來了,因此select()方法對應(yīng)了一條SQL語句中的select部分。
那么上面的一段代碼,查詢出的結(jié)果和如下SQL語句是相同的:
select title,content from news where title = "Android智能手機";
很好玩吧?不過這還不算完呢,我們還可以繼續(xù)連綴更多的東西。比如說,我希望將查詢出的新聞按照發(fā)布的時間倒序排列,即最新發(fā)布的新聞放在最前面,那就可以這樣寫:
List<News> newsList = LitePal.select("title", "content")
.where("title = ? ", "Android智能手機")
.order("publishdate desc").find(News.class);
order()方法中接收一個字符串參數(shù),用于指定查詢出的結(jié)果按照哪一列進行排序,asc表示正序排序,desc表示倒序排序,因此order()方法對應(yīng)了一條SQL語句中的order by部分。
那么上面的一段代碼,查詢出的結(jié)果和如下SQL語句是相同的:
select title,content from news where title="Android智能手機" order by publishdate desc;
然后呢,也許你并不希望將所有條件匹配的結(jié)果一次性全部查詢出來,因為這樣數(shù)據(jù)量可能會有點太大了,而是希望只查詢出前10條數(shù)據(jù),那么使用連綴同樣可以輕松解決這個問題,代碼如下所示:
List<News> newsList = LitePal.select("title", "content")
.where("title= ?", "Android智能手機")
.order("publishdate desc").limit(10).find(News.class);
這里我們又連綴了一個limit()方法,這個方法接收一個整型參數(shù),用于指定查詢前幾條數(shù)據(jù),這里指定成10,意思就是查詢所有匹配結(jié)果中的前10條數(shù)據(jù)。
那么上面的一段代碼,查詢出的結(jié)果和如下SQL語句是相同的:
select title,content from news where title="Android智能手機" order by publishdate desc limit 10;
剛才我們查詢到的是所有匹配條件的前10條新聞,那么現(xiàn)在我想對新聞進行分頁展示,翻到第二頁時,展示第11到第20條新聞,這又該怎么實現(xiàn)呢?沒關(guān)系,在LitePal的幫助下,這些功能都是十分簡單的,只需要再連綴一個偏移量就可以了,如下所示:
List<News> newsList = LitePal.select("title", "content")
.where("title= ?", "Android智能手機")
.order("publishdate desc").limit(10).offset(10)
.find(News.class);
這里我們又添加了一個offset()方法,用于指定查詢結(jié)果的偏移量,這里指定成10,就表示偏移十個位置,那么原來是查詢前10條新聞的,偏移了十個位置之后,就變成了查詢第11到第20條新聞了,如果偏移量是20,那就表示查詢第21到第30條新聞,以此類推。因此,limit()方法和offset()方法共同對應(yīng)了一條SQL語句中的limit部分。
那么上面的一段代碼,查詢出的結(jié)果和如下SQL語句是相同的:
select title,content from news where title="Android智能手機" order by publishdate desc limit 10,10;
這種查詢的好處就在于,我們可以隨意地組合各種查詢參數(shù),需要用到的時候就把它們連綴到一起,不需要用到的時候不用指定就可以了。對比一下query()方法中那冗長的參數(shù)列表,即使我們用不到那些參數(shù),也必須要傳null,是不是明顯感覺LitePal中的查詢更加人性化?
激進查詢
關(guān)聯(lián)表中數(shù)據(jù)是無法查到的,因為LitePal默認(rèn)的模式就是懶查詢,當(dāng)然這也是推薦的查詢方式。那么,如果你真的非常想要一次性將關(guān)聯(lián)表中的數(shù)據(jù)也一起查詢出來,當(dāng)然也是可以的,LitePal中也支持激進查詢的方式,下面我們就來一起看一下。
剛才我們所學(xué)的每一個類型的find()方法,都對應(yīng)了一個帶有isEager參數(shù)的方法重載,這個參數(shù)相信大家一看就明白是什么意思了,設(shè)置成true就表示激進查詢,這樣就會把關(guān)聯(lián)表中的數(shù)據(jù)一起查詢出來了。
比如說,我們想要查詢news表中id為2的新聞,并且把這條新聞所對應(yīng)的評論也一起查詢出來,就可以這樣寫:
News news = LitePal.find(News.class, 2, true);
List<Comment> commentList = news.getCommentList();
這里并沒有什么復(fù)雜的用法,也就是在find()方法的最后多加了一個true參數(shù),就表示使用激進查詢了。這會將和news表關(guān)聯(lián)的所有表中的數(shù)據(jù)也一起查出來,那么comment表和news表是多對一的關(guān)聯(lián),所以使用激進查詢一條新聞的時候,那么該新聞所對應(yīng)的評論也就一起被查詢出來了。
但是這種查詢方式LitePal并不推薦,因為如果一旦關(guān)聯(lián)表中的數(shù)據(jù)很多,查詢速度可能就會非常慢。而且激進查詢只能查詢出指定表的關(guān)聯(lián)表數(shù)據(jù),但是沒法繼續(xù)迭代查詢關(guān)聯(lián)表的關(guān)聯(lián)表數(shù)據(jù)。因此,這里我建議大家還是使用默認(rèn)的懶加載更加合適,至于如何查詢出關(guān)聯(lián)表中的數(shù)據(jù),其實只需要在模型類中做一點小修改就可以了。修改News類中的代碼,如下所示:
public class News extends LitePalSupport{
...
public List<Comment> getComments() {
return LitePal.where("news_id = ?", String.valueOf(id)).find(Comment.class);
}
}
我們在News類中添加了一個getComments()方法,而這個方法的內(nèi)部就是使用了一句連綴查詢,查出了當(dāng)前這條新聞對應(yīng)的所有評論。改成這種寫法之后,我們就可以將關(guān)聯(lián)表數(shù)據(jù)的查詢延遲,當(dāng)我們需要去獲取新聞所對應(yīng)的評論時,再去調(diào)用News的getComments()方法,這時才會去查詢關(guān)聯(lián)數(shù)據(jù)。這種寫法會比激進查詢更加高效也更加合理。
原生查詢
也許你總會遇到一些千奇百怪的需求,可能使用LitePal提供的查詢API無法完成這些需求。沒有關(guān)系,因為即使使用了LitePal,你仍然可以使用原生的查詢方式(SQL語句)來去查詢數(shù)據(jù)。LitePal類中還提供了一個findBySQL()方法,使用這個方法就能通過原生的SQL語句方式來查詢數(shù)據(jù)了,如下所示:
Cursor cursor = LitePal.findBySQL("select * from news where title=?", "Android智能手機");
if (null == cursor) {
Log.d(TAG, "沒有獲取游標(biāo)");
return;
}
while (cursor.moveToNext()) {
int id = cursor.getInt(cursor.getColumnIndex("id"));
String title = cursor.getString(cursor.getColumnIndex("title"));
String content = cursor.getString(cursor.getColumnIndex("content"));
Log.d(TAG, "id=" + id + "----title = " + title + "----content = " + content);
}
findBySQL()方法接收任意個字符串參數(shù),其中第一個參數(shù)就是SQL語句,后面的參數(shù)都是用于替換SQL語句中的占位符的,用法非常簡單。另外,findBySQL()方法返回的是一個Cursor對象,這和原生SQL語句的用法返回的結(jié)果也是相同的。
下一篇文章當(dāng)中會開始講解聚合函數(shù)的用法,感興趣的朋友請繼續(xù)閱讀 Android數(shù)據(jù)庫高手秘籍(八)使用LitePal的聚合函數(shù)