文档章节

SpringBoot 系列教程 Solr 之查询使用姿势小结

小灰灰Blog
 小灰灰Blog
发布于 01/17 09:16
字数 2618
阅读 116
收藏 0

200115-SpringBoot 系列教程 Solr 之查询使用姿势小结

接下来进入 solr CURD 的第四篇,查询的使用姿势介绍,本文将主要包括以下知识点

  • 基本的查询操作
  • fq 查询
  • fl 指定字段查询
  • 比较/范围
  • 排序
  • 分页
  • 分组

<!-- more -->

I. 配置

在介绍 demo 之前,需要先安装 solr 环境,搭建 SpringBoot 项目工程,具体的环境搭建过程不细说,推荐参考文档

application.yml 配置文件中红,指定 solr 的域名

spring:
  data:
    solr:
      host: http://127.0.0.1:8983/solr

然后在 solr 中,写入一些数据,供我们查询使用,可以通过控制台的方式写入,也可以通过190526-SpringBoot 高级篇搜索 Solr 之文档新增与修改使用姿势 这篇文档的 case 添加

初始化 solr 文档内容如下

{
  "id":"1",
  "content_id":1,
  "title":"一灰灰blog",
  "content":"这是一灰灰blog的内容",
  "type":1,
  "create_at":1578912072,
  "publish_at":1578912072,
  "_version_":1655609540674060288},
{
  "id":"2",
  "content_id":2,
  "title":"一灰灰",
  "content":"这是一灰灰的内容",
  "type":1,
  "create_at":1578912072,
  "publish_at":1578912072,
  "_version_":1655609550229733376},
{
  "id":"3",
  "content_id":3,
  "title":"solrTemplate 修改之后!!!",
  "create_at":1578993153,
  "publish_at":1578993153,
  "type":0,
  "_version_":1655694325261008896},
{
  "id":"4",
  "content_id":4,
  "type":1,
  "create_at":0,
  "publish_at":0,
  "_version_":1655694325422489600},
{
  "id":"5",
  "content_id":5,
  "title":"addBatchByBean - 1",
  "content":"新增一个测试文档",
  "type":1,
  "create_at":1578993153,
  "publish_at":1578993153,
  "_version_":1655694325129936896},
{
  "id":"6",
  "content_id":6,
  "title":"addBatchByBean - 2",
  "content":"新增又一个测试文档",
  "type":1,
  "create_at":1578993153,
  "publish_at":1578993153,
  "_version_":1655694325136228352
}

II. 查询

solr 文档对应的 POJO 如下,(注意 solr 中的主键 id 为 string 类型,下面定义中用的是 Integer,推荐与 solr 的数据类型保持一致)

@Data
public class DocDO implements Serializable {
    private static final long serialVersionUID = 7245059137561820707L;
    @Id
    @Field("id")
    private Integer id;
    @Field("content_id")
    private Integer contentId;
    @Field("title")
    private String title;
    @Field("content")
    private String content;
    @Field("type")
    private Integer type;
    @Field("create_at")
    private Long createAt;
    @Field("publish_at")
    private Long publishAt;
}

1. 主键查询

支持单个查询和批量查询,三个参数,第一个为需要查询的 Collection, 第二个为 id/id 集合,第三个为返回的数据类型

private void queryById() {
    DocDO ans = solrTemplate.getById("yhh", 1, DocDO.class).get();
    System.out.println("queryById: " + ans);

    Collection<DocDO> list = solrTemplate.getByIds("yhh", Arrays.asList(1, 2), DocDO.class);
    System.out.println("queryByIds: " + list);
}

输出结果如下

queryById: DocDO(id=1, contentId=1, title=一灰灰blog, content=这是一灰灰blog的内容, type=1, createAt=1578912072, publishAt=1578912072)
queryByIds: [DocDO(id=1, contentId=1, title=一灰灰blog, content=这是一灰灰blog的内容, type=1, createAt=1578912072, publishAt=1578912072), DocDO(id=2, contentId=2, title=一灰灰, content=这是一灰灰的内容, type=1, createAt=1578912072, publishAt=1578912072)]

2. 简单查询

比如最简单的根据某个字段进行查询

Query query = new SimpleQuery("title:一灰灰");
Page<DocDO> ans = solrTemplate.query("yhh", query, DocDO.class);
System.out.println("simpleQuery : " + ans.getContent());

直接在 SimpleQuery 中指定查询条件,上面的 case 表示查询 title 为一灰灰的文档

输出结果如下:

simpleQuery : [DocDO(id=2, contentId=2, title=一灰灰, content=这是一灰灰的内容, type=1, createAt=1578912072, publishAt=1578912072)]

简单的查询使用上面的姿势 ok,当然就是阅读起来不太优雅;推荐另外一种基于Criteria的查询条件构建方式

  • 如果看过之前的 mongodb 系列教程,可以看到 monodb 的查询条件也用到了 Criteria 来拼装,但是请注意这两个并不是一个东西
query = new SimpleQuery();
// 查询内容中包含一灰灰的文档
query.addCriteria(new Criteria("content").contains("一灰灰"));

ans = solrTemplate.query("yhh", query, DocDO.class);
System.out.println("simpleQuery : " + ans.getContent());

输出结果如下

simpleQuery : [DocDO(id=1, contentId=1, title=一灰灰blog, content=这是一灰灰blog的内容, type=1, createAt=1578912072, publishAt=1578912072), DocDO(id=2, contentId=2, title=一灰灰, content=这是一灰灰的内容, type=1, createAt=1578912072, publishAt=1578912072)]

Criteria可以构建复杂的且阅读友好的查询条件,后面会有具体的演示,这里给出一个多条件查询的 case

// 多个查询条件
query = new SimpleQuery();
query.addCriteria(Criteria.where("title").contains("一灰灰").and("content_id").lessThan(2));
ans = solrTemplate.query("yhh", query, DocDO.class);
System.out.println("multiQuery: " + ans.getContent());

输出结果如下,在上面的基础上,捞出了 contentId 小于 2 的记录

multiQuery: [DocDO(id=1, contentId=1, title=一灰灰blog, content=这是一灰灰blog的内容, type=1, createAt=1578912072, publishAt=1578912072)]

3. fq 查询

fq 主要用来快速过滤,配合 query 进行操作,主要是借助org.springframework.data.solr.core.query.Query#addFilterQuery来添加 fq 条件

// fq查询
query = new SimpleQuery("content: *一灰灰*");
query.addFilterQuery(FilterQuery.filter(Criteria.where("title").contains("blog")));
ans = solrTemplate.query("yhh", query, DocDO.class);
System.out.println("simpleQueryAndFilter: " + ans.getContent());

输出结果如:

simpleQueryAndFilter: [DocDO(id=1, contentId=1, title=一灰灰blog, content=这是一灰灰blog的内容, type=1, createAt=1578912072, publishAt=1578912072)]

4. fl 指定查询字段

当我们只关注 solr 文档中的部分字段时,可以考虑指定 fl,只获取所需的字段;通过org.springframework.data.solr.core.query.SimpleQuery#addProjectionOnFields(java.lang.String...)来指定需要返回的字段名

/**
 * 查询指定的字段
 */
private void querySpecialFiled() {
    SimpleQuery query = new SimpleQuery();
    query.addCriteria(Criteria.where("content_id").lessThanEqual(2));
    // fl 查询
    query.addProjectionOnFields("id", "title", "content");

    List<DocDO> ans = solrTemplate.query("yhh", query, DocDO.class).getContent();
    System.out.println("querySpecialField: " + ans);
}

输出结果如下

querySpecialField: [DocDO(id=1, contentId=null, title=一灰灰blog, content=这是一灰灰blog的内容, type=null, createAt=null, publishAt=null), DocDO(id=2, contentId=null, title=一灰灰, content=这是一灰灰的内容, type=null, createAt=null, publishAt=null)]

请注意,我们指定了只需要返回id, title, content,所以返回的 DO 中其他的成员为 null

5. 范围查询

针对数字类型,支持范围查询,比如上面给出Criteria.where("content_id").lessThanEqual(2),表示查询content_id小于 2 的记录,下面给出一个 between 的查询

/**
 * 范围查询
 */
private void queryRange() {
    Query query = new SimpleQuery();
    query.addCriteria(Criteria.where("content_id").between(1, 3));
    query.addSort(Sort.by("content_id").ascending());
    List<DocDO> ans = solrTemplate.query("yhh", query, DocDO.class).getContent();
    System.out.println("queryRange: " + ans);
}

输出结果如下,请注意 between 查询,左右都是闭区间

queryRange: [DocDO(id=1, contentId=1, title=一灰灰blog, content=这是一灰灰blog的内容, type=1, createAt=1578912072, publishAt=1578912072), DocDO(id=2, contentId=2, title=一灰灰, content=这是一灰灰的内容, type=1, createAt=1578912072, publishAt=1578912072), DocDO(id=3, contentId=3, title=solrTemplate 修改之后!!!, content=null, type=0, createAt=1578997659, publishAt=1578997659)]

如果不想要闭区间,可以用between的重载方法

query = new SimpleQuery();
// 两个false,分表表示不包含下界 上界
query.addCriteria(Criteria.where("content_id").between(1, 3, false, false));
query.addSort(Sort.by("content_id").ascending());
ans = solrTemplate.query("yhh", query, DocDO.class).getContent();
System.out.println("queryRange: " + ans);

输出结果如

queryRange: [DocDO(id=2, contentId=2, title=一灰灰, content=这是一灰灰的内容, type=1, createAt=1578912072, publishAt=1578912072)]

6. 排序

上面的 case 中,已经用到了排序,主要是Sort来指定排序字段以及排序的方式;因为 id 在 solr 中实际上是字符串格式,所以如果用 id 进行排序时,实际上是根据字符串的排序规则来的(虽然我们的 POJO 中 id 为 int 类型)

/**
 * 查询并排序
 */
private void queryAndSort() {
    // 排序
    Query query = new SimpleQuery();
    query.addCriteria(new Criteria("content").contains("一灰灰"));
    // 倒排
    query.addSort(Sort.by("content_id").descending());
    Page<DocDO> ans = solrTemplate.query("yhh", query, DocDO.class);
    System.out.println("queryAndSort: " + ans.getContent());
}

输出结果如下

queryAndSort: [DocDO(id=2, contentId=2, title=一灰灰, content=这是一灰灰的内容, type=1, createAt=1578912072, publishAt=1578912072), DocDO(id=1, contentId=1, title=一灰灰blog, content=这是一灰灰blog的内容, type=1, createAt=1578912072, publishAt=1578912072)]

7. 分页查询

分页查询比较常见,特别是当数据量比较大时,请一定记得,添加分页条件

一个查询 case 如下,查询所有的数据,并制定了分页条件,查询第二条和第三条数据(计数从 0 开始)

/**
 * 分页
 */
private void queryPageSize() {
    Query query = new SimpleQuery("*:*");
    query.addSort(Sort.by("content_id").ascending());
    // 指定偏移量,从0开始
    query.setOffset(2L);
    // 查询的size数量
    query.setRows(2);
    Page<DocDO> ans = solrTemplate.queryForPage("yhh", query, DocDO.class);

    // 文档数量
    long totalDocNum = ans.getTotalElements();
    List<DocDO> docList = ans.getContent();
    System.out.println("queryPageSize:  totalDocNum=" + totalDocNum + " docList=" + docList);
}

在返回结果中,查了返回查询的文档之外,还会给出满足条件的文档数量,可以通过Page#getTotalElements获取,

上面 case 输出结果如下

queryPageSize:  totalDocNum=6 docList=[DocDO(id=3, contentId=3, title=solrTemplate 修改之后!!!, content=null, type=0, createAt=1578997946, publishAt=1578997946), DocDO(id=4, contentId=4, title=null, content=null, type=1, createAt=0, publishAt=0)]

8. 分组查询

分组和前面的查询有一点区别,主要在于结果的处理,以及分组参数必须指定分页信息

/**
 * 分组查询
 */
private void queryGroup() {
    Query query = new SimpleQuery("*:*");
    // 请注意,分组查询,必须指定 offset/limit, 否则会抛异常,Pageable must not be null!
    GroupOptions groupOptions = new GroupOptions().addGroupByField("type").setOffset(0).setLimit(10);
    query.setGroupOptions(groupOptions);

    GroupPage<DocDO> ans = solrTemplate.queryForGroupPage("yhh", query, DocDO.class);
    GroupResult<DocDO> groupResult = ans.getGroupResult("type");

    Page<GroupEntry<DocDO>> entries = groupResult.getGroupEntries();
    System.out.println("============ query for group ============ ");
    for (GroupEntry<DocDO> sub : entries) {
        // type 的具体值
        String groupValue = sub.getGroupValue();
        Page<DocDO> contentList = sub.getResult();
        System.out.println("queryGroup v=" + groupValue + " content=" + contentList.getContent());
    }
    System.out.println("============ query for group ============ ");
}

上面的 case 虽然比较简单,但是有几点需要注意, 特别是返回结果的获取,包装层级有点深

  • GroupOptions:
    • 必须指定 offset/limit,当两个条件都没有时会抛异常
    • 只指定 offset 时,limit 默认为 1
    • 只指定 limit 时,offset 默认为 0
  • 结果处理
    • GroupPage#getGroupResult(field) 获取分组内容,其中 field 为指定分组的成员
    • 遍历GroupResult#getGroupEntries,获取每个分组对应的文档列表

输出结果如下

============ query for group ============
queryGroup v=1 content=[DocDO(id=1, contentId=1, title=一灰灰blog, content=这是一灰灰blog的内容, type=1, createAt=1578912072, publishAt=1578912072), DocDO(id=2, contentId=2, title=一灰灰, content=这是一灰灰的内容, type=1, createAt=1578912072, publishAt=1578912072), DocDO(id=5, contentId=5, title=addBatchByBean - 1, content=新增一个测试文档, type=1, createAt=1578997946, publishAt=1578997946), DocDO(id=6, contentId=6, title=addBatchByBean - 2, content=新增又一个测试文档, type=1, createAt=1578997946, publishAt=1578997946), DocDO(id=4, contentId=4, title=null, content=null, type=1, createAt=0, publishAt=0)]
queryGroup v=0 content=[DocDO(id=3, contentId=3, title=solrTemplate 修改之后!!!, content=null, type=0, createAt=1578997946, publishAt=1578997946)]
============ query for group ============

III. 其他

0. 系列博文&工程源码

系列博文

工程源码

1. 一灰灰 Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

一灰灰blog

© 著作权归作者所有

小灰灰Blog

小灰灰Blog

粉丝 236
博文 281
码字总数 508415
作品 0
武汉
程序员
私信 提问
加载中

评论(0)

SpringBoot 系列教程 Solr 之文档删除

200114-SpringBoot 系列教程 Solr 之文档删除 之前的搜索教程开了个头就没有继续了,现在重新捡回来,至少也把 CURD 的基本操作姿势补全了;本篇主要介绍如何删除数据 I. 配置 在介绍 demo 之...

小灰灰Blog
01/16
37
0
【SpringBoot 搜索系列】Solr 身份认证与授权更新异常解决方案

【搜索系列】Solr 身份认证与授权更新异常解决方案 之前介绍 solr 的教程中,solr 没有开启权限校验,所有的操作都是无需鉴权;当时提到,如果 solr 开启了权限校验,改一下 solr 的 host,带...

小灰灰Blog
04/01
7
0
SpringBoot 系列教程之编程式事务使用姿势介绍篇

SpringBoot 系列教程之编程式事务使用姿势介绍篇 前面介绍的几篇事务的博文,主要是利用注解的声明式使用姿势,其好处在于使用简单,侵入性低,可辨识性高(一看就知道使用了事务);然而缺点...

小灰灰Blog
02/05
65
0
SpringBoot 系列 web 篇之自定义返回 Http Code 的 n 种姿势

SpringBoot 系列 web 篇之自定义返回 Http Code 的 n 种姿势 虽然 http 的提供了一整套完整、定义明确的状态码,但实际的业务支持中,后端并不总会遵守这套规则,更多的是在返回结果中,加一...

小灰灰Blog
01/14
81
0
【SpringBoot WEB 系列】SSE 服务器发送事件详解

【SpringBoot WEB系列】SSE 服务器发送事件详解 SSE 全称,直译一下就是服务器发送事件,一般的项目开发中,用到的机会不多,可能很多小伙伴不太清楚这个东西,到底是干啥的,有啥用 本文主要...

小灰灰Blog
04/02
21
0

没有更多内容

加载失败,请刷新页面

加载更多

检查是否已使用jQuery选中复选框 - Check if checkbox is checked with jQuery

问题: How can I check if a checkbox in a checkbox array is checked using the id of the checkbox array? 如何检查是否使用复选框数组的ID选中了复选框数组中的复选框? I am using the...

技术盛宴
17分钟前
3
0
略谈分布式系统中的容器设计模式

本文作者:zytan_cocoa 略谈分布式系统中的容器设计模式 谭中意 2020/3/5 前言:云原生(Cloud Native)不仅仅是趋势,更是现在进行时,它是构建现代的,可弹性伸缩的,快速迭代的计算网络服...

百度开发者中心
03/11
18
0
创建将T约束为枚举的泛型方法 - Create Generic method constraining T to an Enum

问题: I'm building a function to extend the Enum.Parse concept that 我正在构建一个函数来扩展Enum.Parse概念, Allows a default value to be parsed in case that an Enum value is ......

javail
今天
31
0
算法 : 一个整形一维数组,以及一个目标数,写一个方法,求出数组元素之和为该目标值的所有组合。

一个整形一维数组,以及一个目标数,写一个方法,求出数组任意两个元素之和为该目标值的所有组合。 function meetGroup($array, $hit){ $meet_groups =[]; foreach ($array as $...

冻结not
今天
35
0
win10下docker中安装sqlserver并使用navicat premiun连接

搜索镜像:docker search mssql 下载镜像:docker pull microsoft/mssql-server-linux 运行镜像:docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=[包含大小写字母、数字的长度不少于8位数的......

nie_yuange
今天
28
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部