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

 

經過瞭多篇文章的學習,我們已經把LitePal中的絕大部分內容都掌握瞭。現在回想起來瞭,增刪改查四種操作中的前三種我們都已經學完瞭,不知道現在使用起數據庫來,你有沒有感覺到格外的輕松和簡單。但是呢,我們都知道,在所有的數據庫操作當中,查詢操作肯定是最復雜的,用法也是最多的,因此LitePal在查詢方面提供的API也是比較豐富,而且LitePal在查詢方面的API設計也是頗為藝術的。那麼今天我們就專門使用一篇博客來講解一下查詢操作的用法,體驗一下LitePal查詢的藝術。還沒有看過前面一篇文章的朋友建議先去參考 Android數據庫高手秘籍(六)——LitePal的修改和刪除操作

 

傳統的查詢數據方式

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

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

其中,rawQuery()方法接收兩個參數,第一個參數接收的就是一個SQL字符串,第二個參數是用於替換SQL語句中占位符(?)的字符串數組。rawQuery()方法返回一個Cursor對象,所有查詢到的數據都是封閉在這個對象當中的,我們隻要一一取出就可以瞭。

 

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

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表中的所有數據,就應該要這樣寫:

SQLiteDatabase db = dbHelper.getWritableDatabase();
Cursor cursor = db.query(news, null, null, null, null, null, null);

可以看到,將第一個表名參數指定成news,然後後面的六個參數我們都用不到,就全部指定成null。

那如果是我們想查詢news表中所有評論數大於零的新聞該怎麼寫呢?代碼如下所示:

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 newsList = new ArrayList();
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就可以這樣寫:

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為1、3、5、7的數據都查出來,該怎麼寫呢?也許有的朋友會比較聰明,立馬就想到可以一個個去查,就調用四次find()方法嘛,然後把1、3、5、7這四個id分別傳進去不就可以瞭。沒錯,這樣做完全是可以的,而且效率也並不低,但是LitePal給我們提供瞭一個更簡便的方法——findAll()。這個方法的用法和find()方法是非常類似的,隻不過它可以指定多個id,並且返回值也不再是一個泛型類對象,而是一個泛型類集合,如下所示:

List 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 newsList = DataSupport.findAll(News.class, ids);

看到這裡,那有的朋友可能會奇怪瞭,說findAll()方法不應該是查詢所有數據的意思嗎?怎麼總是查詢幾個id所對應數據呢?哈!這個問題問得好,因為findAll()方法也是可以查詢所有數據的,而且查詢所有數據的寫法更簡單,隻需要這樣寫:

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

看到沒有,我們隻需要把後面的參數都去掉,在不指定具體id的情況下,findAll()方法查詢出的就是news表中的所有數據瞭,是不是語義性非常強?

 

而且大傢不要以為剛才這些都隻是findAll()的幾個方法重載而已,實際上剛才我們的這幾種用法都是調用的同一個findAll()方法!一個方法卻能夠實現多種不同的查詢效果,並且語義性也很強,讓人一看就能理解,這就是LitePal的查詢藝術!

連綴查詢

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

為瞭避免冗長的參數列表,LitePal采用瞭一種非常巧妙的解決方案,叫作連綴查詢,這種查詢很靈活,可以根據我們實際的查詢需求來動態配置查詢參數。 那這裡舉個簡單的例子,比如我們想查詢news表中所有評論數大於零的新聞,就可以這樣寫:

List 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 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 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 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 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語句是相同的:

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 commentList = news.getCommentList();

可以看到,這裡並沒有什麼復雜的用法,也就是在find()方法的最後多加瞭一個true參數,就表示使用激進查詢瞭。這會將和news表關聯的所有表中的數據也一起查出來,那麼comment表和news表是多對一的關聯,所以使用激進查詢一條新聞的時候,那麼該新聞所對應的評論也就一起被查詢出來瞭。

 

激進查詢的用法非常簡單,就隻有這麼多,其它find()方法也都是同樣的用法,就不再重復介紹瞭。但是這種查詢方式LitePal並不推薦,因為如果一旦關聯表中的數據很多,查詢速度可能就會非常慢。而且激進查詢隻能查詢出指定表的關聯表數據,但是沒法繼續迭代查詢關聯表的關聯表數據。因此,這裡我建議大傢還是使用默認的懶加載更加合適,至於如何查詢出關聯表中的數據,其實隻需要在模型類中做一點小修改就可以瞭。修改News類中的代碼,如下所示:

public class News extends DataSupport{
	
	...

	public List 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中提供的查詢數據的方法全部都學完瞭,那麼今天的文章就到這裡,下一篇文章當中會開始講解聚合函數的用法。

 

LitePal開源項目地址:https://github.com/LitePalFramework/LitePal

 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。