接下来进入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 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 )); 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(); 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()); query.setOffset(2L ); 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("*:*" ); 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) { 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
Gitalking ...