【搜索系列】Solr查询使用姿势小结

文章目录
  1. I. 配置
  2. II. 查询
    1. 1. 主键查询
    2. 2. 简单查询
    3. 3. fq查询
    4. 4. fl指定查询字段
    5. 5. 范围查询
    6. 6. 排序
    7. 7. 分页查询
    8. 8. 分组查询
  3. III. 其他
    1. 0. 系列博文&工程源码
    2. 1. 一灰灰Blog

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

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

I. 配置

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

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

1
2
3
4
spring:
data:
solr:
host: http://127.0.0.1:8983/solr

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

初始化solr文档内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
{
"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的数据类型保持一致)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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集合,第三个为返回的数据类型

1
2
3
4
5
6
7
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);
}

输出结果如下

1
2
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. 简单查询

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

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

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

输出结果如下:

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

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

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

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

输出结果如下

1
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

1
2
3
4
5
// 多个查询条件
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的记录

1
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条件

1
2
3
4
5
// 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());

输出结果如:

1
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...)来指定需要返回的字段名

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 查询指定的字段
*/
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);
}

输出结果如下

1
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的查询

1
2
3
4
5
6
7
8
9
10
/**
* 范围查询
*/
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查询,左右都是闭区间

1
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的重载方法

1
2
3
4
5
6
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);

输出结果如

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

6. 排序

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

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 查询并排序
*/
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());
}

输出结果如下

1
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开始)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 分页
*/
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输出结果如下

1
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. 分组查询

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 分组查询
*/
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,获取每个分组对应的文档列表

输出结果如下

1
2
3
4
============ 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


打赏 如果觉得我的文章对您有帮助,请随意打赏。
分享到