Android数据库高手秘籍(七)LitePal的查询艺术

现在回想起来,增删改查四种操作中的前三种我们都已经学完了,有没有感觉到格外的轻松和简单。LitePal在查询方面提供的API也是比较丰富,而且LitePal在查询方面的API设计也是颇为艺术的。还没有看过前面一篇文章的朋友建议先去参考 Android数据库高手秘籍(六)LitePal的修改和删除操作

LitePal的项目地址是:https://github.com/LitePalFramework/LitePal

logo.png

传统的查询数据方式

最传统的查询数据的方式当然是使用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<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());
}

这是传统查询数据方式的用法了,总体来看,用法确实非常不友好,尤其是query()方法冗长的参数列表,即使我们用不到那些参数,也必须要传入许多个null。另外,查询到的数据还都只是封装到了一个Cursor对象中,我们还需要将数据一 一取出然后再set到实体类对象当中。麻烦吗?可能你觉得不麻烦,因为你已经习惯了这种用法。但是习惯总是可以改变的,也许当你体验了LitePal中查询API给我们带来的便利之后,就会有了新的看法了,那么下面我们就一起来体验一下LitePal的查询艺术。

使用LitePal查询数据

LitePal在查询方面提供了非常丰富的API,基本上已经能够满足我们平时所有的查询需求了。不仅如此,LitePal在查询API的设计方面也是非常用心,摒弃了原生query()方法中繁琐的参数列表,而是改用了一种更为灵巧的方式 连缀查询。除此之外,LitePal查询的结果也不再返回Cursor对象,而是直接返回封装好的对象。这些改变都使得查询数据变得更加简单,也更加合理,那么下面我们就来完整地学习一下LitePal中查询数据的所有用法。

简单查询

比如说查询news表中id为1的这条记录,使用LitePal就可以这样写:

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

仅仅一行代码,就可以把news表中id为1的记录查出来了,而且结果还是自动封装到News对象里的,也不需要我们手动再从Cursor中去解析。如果是用原生的SQL语句,或者query()方法来写,要写一大推!

可以看到,它的参数列表也比较简单,只接收两个参数,这里传入News.class,那么返回的对象也就是News了。第二个参数就更简单了,就是一个id值,如果想要查询id为1的记录就传1,想查id为2的记录就传2,以此类推。

你也许遇到过以下场景,在某些情况下,你需要取出表中的第一条数据,那么传统的做法是怎么样的呢?在SQL语句中指定一个limit值,然后获取返回结果的第一条记录。但是在LitePal中不用这么麻烦,比如我们想要获取news表中的第一条数据,只需要这样写:

News first = LitePal.findFirst(News.class);

只需调用findFirst()方法,然后传入News类,得到的就是news表中的第一条数据了。

那我们举一反三,如果是想要获取News表中的最后一条数据该怎么写呢?同样简单,如下所示:

News last = LitePal.findLast(News.class);

获取表中第一条或者是最后一条数据的场景比较常见,所以LitePal特意提供了这两个方法来方便我们的操作。

目前都只是查询单条数据的功能,如果想要查询多条数据该怎么办呢?比如说,我们想把news表中id为1、3、5、7的数据都查出来,该怎么写呢?LitePal给我们提供了一个更简便的方法——findAll()。这个方法的用法和find()方法是非常类似的,只不过它可以指定多个id,并且返回值也不再是一个泛型类对象,而是一个泛型类集合,如下所示:

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

可以看到,首先我们是调用的findAll()方法,然后这个方法的第一个参数仍然是指定的泛型类,但是后面的参数就很随意了,你可以传入任意个id进去,findAll()方法会把所有传入的id所对应的数据全部查出来,然后一起返回到List<News>这个泛型集合当中。

findAll()方法也是可以查询所有数据的,而且查询所有数据的写法更简单,只需要这样写:

List<News> all = LitePal.findAll(News.class);

我们只需要把后面的参数都去掉,在不指定具体id的情况下,findAll()方法查询出的就是news表中的所有数据了,是不是语义性非常强?

而且大家不要以为刚才这些都只是findAll()的几个方法重载而已,实际上刚才我们的这几种用法都是调用的同一个findAll()方法!一个方法却能够实现多种不同的查询效果,并且语义性也很强,让人一看就能理解,这就是LitePal的查询艺术!

连缀查询

LitePal给我们提供的查询功能还远远不只这些,不能随意地指定查询条件。让我们回想一下传统情况应该怎么做,query()方法中接收七个参数,其中第三和第四个参数就是用于指定查询条件的,然后其它几个参数都填null就可以了。但是呢,前面我们已经痛批过了这种写法,因为冗长的参数列表太过繁琐,那么LitePal又是怎么解决这个问题的呢?我们现在就来学习一下。

为了避免冗长的参数列表,LitePal采用了一种非常巧妙的解决方案,叫作连缀查询,这种查询很灵活,可以根据我们实际的查询需求来动态配置查询参数。 那这里举个简单的例子,比如我们想查询news表中所有标题等于Android智能手机的 新闻,就可以这样写:

List<News> newsList = LitePal.where("title = 'Android智能手机' ").find(News.class);

可以看到,首先是调用了LitePal的where()方法,在这里指定了查询条件。where()方法接收任意个字符串参数,其中第一个参数用于进行条件约束,从第二个参数开始,都是用于替换第一个参数中的占位符的。那这个where()方法就对应了一条SQL语句中的where部分。

接着我们在where()方法之后直接连缀了一个find()方法,然后在这里指定一个泛型类,表示用于查询哪张表。那么上面的一段代码,查询出的结果和如下SQL语句是相同的:

select * from  news where title = "'Android智能手机";

但是这样会将news表中所有的列都查询出来,也许你并不需要那么多的数据,而是只要title和content这两列数据。那么也很简单,我们只要再增加一个连缀就行了,如下所示:

List<News> newsList = LitePal.select("title", "content")
                .where("title = ? ", "Android智能手机").find(News.class);
        for (News news1 : newsList) {
            Log.d(TAG, news1.toString());
        }

可以看到,这里我们新增了一个select()方法,这个方法接收任意个字符串参数,每个参数要求对应一个列名,这样就只会把相应列的数据查询出来了,因此select()方法对应了一条SQL语句中的select部分。

那么上面的一段代码,查询出的结果和如下SQL语句是相同的:

select title,content from news where title = "Android智能手机";

很好玩吧?不过这还不算完呢,我们还可以继续连缀更多的东西。比如说,我希望将查询出的新闻按照发布的时间倒序排列,即最新发布的新闻放在最前面,那就可以这样写:

List<News> newsList = LitePal.select("title", "content")
        .where("title = ? ", "Android智能手机")
        .order("publishdate desc").find(News.class);

order()方法中接收一个字符串参数,用于指定查询出的结果按照哪一列进行排序,asc表示正序排序,desc表示倒序排序,因此order()方法对应了一条SQL语句中的order by部分。

那么上面的一段代码,查询出的结果和如下SQL语句是相同的:

select title,content from news where title="Android智能手机" order by publishdate desc;

然后呢,也许你并不希望将所有条件匹配的结果一次性全部查询出来,因为这样数据量可能会有点太大了,而是希望只查询出前10条数据,那么使用连缀同样可以轻松解决这个问题,代码如下所示:

List<News> newsList = LitePal.select("title", "content")
                .where("title= ?", "Android智能手机")
                .order("publishdate desc").limit(10).find(News.class);

这里我们又连缀了一个limit()方法,这个方法接收一个整型参数,用于指定查询前几条数据,这里指定成10,意思就是查询所有匹配结果中的前10条数据。

那么上面的一段代码,查询出的结果和如下SQL语句是相同的:

select title,content from news where title="Android智能手机" order by publishdate desc limit 10;

刚才我们查询到的是所有匹配条件的前10条新闻,那么现在我想对新闻进行分页展示,翻到第二页时,展示第11到第20条新闻,这又该怎么实现呢?没关系,在LitePal的帮助下,这些功能都是十分简单的,只需要再连缀一个偏移量就可以了,如下所示:

List<News> newsList = LitePal.select("title", "content")
         .where("title= ?", "Android智能手机")
        .order("publishdate desc").limit(10).offset(10)
        .find(News.class);

这里我们又添加了一个offset()方法,用于指定查询结果的偏移量,这里指定成10,就表示偏移十个位置,那么原来是查询前10条新闻的,偏移了十个位置之后,就变成了查询第11到第20条新闻了,如果偏移量是20,那就表示查询第21到第30条新闻,以此类推。因此,limit()方法和offset()方法共同对应了一条SQL语句中的limit部分。

那么上面的一段代码,查询出的结果和如下SQL语句是相同的:

select title,content from news where title="Android智能手机" order by publishdate desc limit 10,10;

这种查询的好处就在于,我们可以随意地组合各种查询参数,需要用到的时候就把它们连缀到一起,不需要用到的时候不用指定就可以了。对比一下query()方法中那冗长的参数列表,即使我们用不到那些参数,也必须要传null,是不是明显感觉LitePal中的查询更加人性化?

激进查询

关联表中数据是无法查到的,因为LitePal默认的模式就是懒查询,当然这也是推荐的查询方式。那么,如果你真的非常想要一次性将关联表中的数据也一起查询出来,当然也是可以的,LitePal中也支持激进查询的方式,下面我们就来一起看一下。

刚才我们所学的每一个类型的find()方法,都对应了一个带有isEager参数的方法重载,这个参数相信大家一看就明白是什么意思了,设置成true就表示激进查询,这样就会把关联表中的数据一起查询出来了。

比如说,我们想要查询news表中id为2的新闻,并且把这条新闻所对应的评论也一起查询出来,就可以这样写:

News news = LitePal.find(News.class, 2, true);
List<Comment> commentList = news.getCommentList();

这里并没有什么复杂的用法,也就是在find()方法的最后多加了一个true参数,就表示使用激进查询了。这会将和news表关联的所有表中的数据也一起查出来,那么comment表和news表是多对一的关联,所以使用激进查询一条新闻的时候,那么该新闻所对应的评论也就一起被查询出来了。

但是这种查询方式LitePal并不推荐,因为如果一旦关联表中的数据很多,查询速度可能就会非常慢。而且激进查询只能查询出指定表的关联表数据,但是没法继续迭代查询关联表的关联表数据。因此,这里我建议大家还是使用默认的懒加载更加合适,至于如何查询出关联表中的数据,其实只需要在模型类中做一点小修改就可以了。修改News类中的代码,如下所示:

public class News extends LitePalSupport{
    
    ...
 
    public List<Comment> getComments() {
        return LitePal.where("news_id = ?", String.valueOf(id)).find(Comment.class);
    }
    
}

我们在News类中添加了一个getComments()方法,而这个方法的内部就是使用了一句连缀查询,查出了当前这条新闻对应的所有评论。改成这种写法之后,我们就可以将关联表数据的查询延迟,当我们需要去获取新闻所对应的评论时,再去调用News的getComments()方法,这时才会去查询关联数据。这种写法会比激进查询更加高效也更加合理。

原生查询

也许你总会遇到一些千奇百怪的需求,可能使用LitePal提供的查询API无法完成这些需求。没有关系,因为即使使用了LitePal,你仍然可以使用原生的查询方式(SQL语句)来去查询数据。LitePal类中还提供了一个findBySQL()方法,使用这个方法就能通过原生的SQL语句方式来查询数据了,如下所示:

Cursor cursor = LitePal.findBySQL("select * from news where  title=?", "Android智能手机");
        if (null == cursor) {
            Log.d(TAG, "没有获取游标");
            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()方法接收任意个字符串参数,其中第一个参数就是SQL语句,后面的参数都是用于替换SQL语句中的占位符的,用法非常简单。另外,findBySQL()方法返回的是一个Cursor对象,这和原生SQL语句的用法返回的结果也是相同的。

下一篇文章当中会开始讲解聚合函数的用法,感兴趣的朋友请继续阅读 Android数据库高手秘籍(八)使用LitePal的聚合函数

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,547评论 4 374
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,787评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,175评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,752评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,169评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,056评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,195评论 2 321
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,980评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,724评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,872评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,344评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,667评论 3 264
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,379评论 3 245
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,202评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,992评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,189评论 2 286
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,987评论 2 279

推荐阅读更多精彩内容