接下來進入 solr CURD 的第四篇,查詢的使用姿勢介紹,本文將主要包括以下知識點
- 基本的查詢操作
- fq 查詢
- fl 指定字段查詢
- 比較/范圍
- 排序
- 分頁
- 分組
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. 系列博文&工程源碼
系列博文
- 200114-SpringBoot 系列教程 Solr 之文檔刪除
- 190526-SpringBoot 高級篇搜索 Solr 之文檔新增與修改使用姿勢
- 190510-SpringBoot 高級篇搜索之 Solr 環境搭建與簡單測試
工程源碼
- 工程:https://github.com/liuyueyi/spring-boot-demo
- 源碼: https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/140-search-solr
1. 一灰灰 Blog
盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的個人博客,記錄所有學習和工作中的博文,歡迎大家前去逛逛
- 一灰灰 Blog 個人博客 https://blog.hhui.top
- 一灰灰 Blog-Spring 專題博客 http://spring.hhui.top