使用 Java 查询 ES
# 70.使用 Java 查询 ES
接下来我们说说更常见的操作:查询
# 查询步骤
不管是根据什么方式查询,步骤都是类似的:
- 创建一个 Client 对象
- 创建一个查询对象,可以使用 QueryBuilders 工具类创建 QueryBuilder 对象。
- 使用 Client 执行查询
- 得到查询的结果
- 取查询结果的总记录数
- 取查询结果列表
- 关闭 Client
我们先创建初始化和销毁方法,减少重复代码:
package com.peterjxl.es;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.junit.After;
import org.junit.Before;
import java.net.InetAddress;
public class SearchIndex {
private TransportClient client;
@Before
public void init() throws Exception{
Settings settings = Settings.builder().put("cluster.name", "my-elasticsearch").build();
client = new PreBuiltTransportClient(settings)
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9301))
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9302))
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9303));
}
@After
public void close() {
client.close();
}
}
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
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
# 根据 id 搜索
示例代码:
@Test
public void searchIndexWithID() {
QueryBuilder queryBuilder = QueryBuilders.idsQuery().addIds("3", "4");
SearchResponse searchResponse = client.prepareSearch("index_hello")
.setTypes("article")
.setQuery(queryBuilder)
.get();
SearchHits searchHits = searchResponse.getHits();
System.out.println("查询结果总记录数" + searchHits.getTotalHits());
Iterator<SearchHit> iterator = searchHits.iterator();
while (iterator.hasNext()) {
SearchHit searchHit = iterator.next();
System.out.println(searchHit.getSourceAsString());
System.out.println("文档的属性: ");
Map<String, Object> document = searchHit.getSource();
System.out.println("id:" + document.get("id"));
System.out.println("title:" + document.get("title"));
System.out.println("content:" + document.get("content"));
System.out.println("-------------------------- ");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
关键在于:
QueryBuilder queryBuilder = QueryBuilders.idsQuery().addIds("3", "4");
1
运行结果:
查询结果总记录数2
{"id":4,"title":"关于买房","content":"虚假的挥霍:花几千块钱去看看海。真正的挥霍:花100万交个首付买一个名义面积70平,实际面积55平,再还30年贷款的小户型住房"}
文档的属性:
id:4
title:关于买房
content:虚假的挥霍:花几千块钱去看看海。真正的挥霍:花100万交个首付买一个名义面积70平,实际面积55平,再还30年贷款的小户型住房
--------------------------
{"id":3,"title":"关于梦想","content":"编程对我而言,就像是一颗小小的,微弱的希望的种子,我甚至都不愿意让人看见它。生怕有人看见了便要嘲讽它,它太脆弱了,经不起别人的质疑"}
文档的属性:
id:3
title:关于梦想
content:编程对我而言,就像是一颗小小的,微弱的希望的种子,我甚至都不愿意让人看见它。生怕有人看见了便要嘲讽它,它太脆弱了,经不起别人的质疑
--------------------------
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 根据 Term 查询(关键词)
QueryBuilder queryBuilder = QueryBuilders.termQuery("title", "北方");
@Test
public void searchIndexWithTerm() {
QueryBuilder queryBuilder = QueryBuilders.termQuery("title", "梦想");
SearchResponse searchResponse = client.prepareSearch("index_hello")
.setTypes("article")
.setQuery(queryBuilder)
.get();
SearchHits searchHits = searchResponse.getHits();
System.out.println("查询结果总记录数" + searchHits.getTotalHits());
Iterator<SearchHit> iterator = searchHits.iterator();
while (iterator.hasNext()) {
SearchHit searchHit = iterator.next();
System.out.println(searchHit.getSourceAsString());
System.out.println("文档的属性: ");
Map<String, Object> document = searchHit.getSource();
System.out.println("id:" + document.get("id"));
System.out.println("title:" + document.get("title"));
System.out.println("content:" + document.get("content"));
System.out.println("-------------------------- ");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
关键在于:
QueryBuilder queryBuilder = QueryBuilders.termQuery("title", "梦想");
1
# 抽取通用代码
我们可以看到,通过 id 和通过 term 查询,只有 QueryBuilder 是不一样的,其他的查询和打印都是一样的,我们可以做成一个通用的方法:
public void search(QueryBuilder queryBuilder){
SearchResponse searchResponse = client.prepareSearch("index_hello")
.setTypes("article")
.setQuery(queryBuilder)
.get();
SearchHits searchHits = searchResponse.getHits();
System.out.println("查询结果总记录数" + searchHits.getTotalHits());
for (SearchHit searchHit : searchHits) {
System.out.println(searchHit.getSourceAsString());
System.out.println("文档的属性: ");
Map<String, Object> document = searchHit.getSource();
System.out.println("id:" + document.get("id"));
System.out.println("title:" + document.get("title"));
System.out.println("content:" + document.get("content"));
System.out.println("-------------------------- ");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
这样其他两个方法就简单了:
@Test
public void searchIndexWithID() {
QueryBuilder queryBuilder = QueryBuilders.idsQuery().addIds("3", "4");
search(queryBuilder);
}
@Test
public void searchIndexWithTerm() {
QueryBuilder queryBuilder = QueryBuilders.termQuery("title", "梦想");
search(queryBuilder);
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# QueryString 查询方式(带分析的查询)
示例:
@Test
public void searchIndexWithQueryString() {
QueryBuilder queryBuilder = QueryBuilders.queryStringQuery("梦想是什么");
search(queryBuilder);
}
1
2
3
4
5
2
3
4
5
此时会在所有域中进行查询,查询结果:
查询结果总记录数1
{"id":3,"title":"关于梦想","content":"编程对我而言,就像是一颗小小的,微弱的希望的种子,我甚至都不愿意让人看见它。生怕有人看见了便要嘲讽它,它太脆弱了,经不起别人的质疑"}
文档的属性:
id:3
title:关于梦想
content:编程对我而言,就像是一颗小小的,微弱的希望的种子,我甚至都不愿意让人看见它。生怕有人看见了便要嘲讽它,它太脆弱了,经不起别人的质疑
--------------------------
1
2
3
4
5
6
7
2
3
4
5
6
7
如果要指定默认搜索域:
QueryBuilder queryBuilder = QueryBuilders.queryStringQuery("梦想是什么").defaultField("title");
1
# 分页的处理
有时候查询出来多个结果,此时就需要分页:在 Client 对象执行查询之前,设置分页信息,然后再执行查询
# 添加多条数据
为了更好的演示分页效果,我们使用循环添加多条数据。在 ElasticsearchClientTest.java
中添加方法:
@Test
public void testAddDocument3() throws Exception {
for (int i = 5; i < 100; i++){
// 创建一个Article对象,设置对象的属性
Article article = new Article();
article.setId((long) i);
article.setTitle("关于生活" + i);
article.setContent("1对夫妻2个打工人带3个孩子养4个父母月薪5千掏空6个钱包7天无休8十万买的房子不到9十平米生活10分困难" + i);
// 把Article对象转换为JSON格式的字符串
ObjectMapper objectMapper = new ObjectMapper();
String jsonDocument = objectMapper.writeValueAsString(article);
// 把文档写入索引库
client.prepareIndex("index_hello", "article", article.getId().toString())
.setSource(jsonDocument, XContentType.JSON)
.get();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
此时能看到有很多数据了:
# 设置分页
即使不设置分页,默认也是会分页,每页 10 条(读者可以自行测试下)。
@Test
public void searchIndexWithPaging() {
QueryBuilder queryBuilder = QueryBuilders.queryStringQuery("生活是什么").defaultField("title");
SearchResponse searchResponse = client.prepareSearch("index_hello")
.setTypes("article")
.setQuery(queryBuilder)
.setFrom(0) // 起始记录下标
.setSize(5) // 每页显示的记录数
.get();
SearchHits searchHits = searchResponse.getHits();
System.out.println("查询结果总记录数" + searchHits.getTotalHits());
for (SearchHit searchHit : searchHits) {
System.out.println(searchHit.getSourceAsString());
System.out.println("文档的属性: ");
Map<String, Object> document = searchHit.getSource();
System.out.println("id:" + document.get("id"));
System.out.println("title:" + document.get("title"));
System.out.println("content:" + document.get("content"));
System.out.println("-------------------------- ");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
分页需要设置两个值,from:起始的行号,从 0 开始。size:每页显示的记录数。查询结果:
查询结果总记录数95
{"id":35,"title":"关于生活35","content":"1对夫妻2个打工人带3个孩子养4个父母月薪5千掏空6个钱包7天无休8十万买的房子不到9十平米生活10分困难35"}
文档的属性:
id:35
title:关于生活35
content:1对夫妻2个打工人带3个孩子养4个父母月薪5千掏空6个钱包7天无休8十万买的房子不到9十平米生活10分困难35
--------------------------
{"id":36,"title":"关于生活36","content":"1对夫妻2个打工人带3个孩子养4个父母月薪5千掏空6个钱包7天无休8十万买的房子不到9十平米生活10分困难36"}
文档的属性:
id:36
title:关于生活36
content:1对夫妻2个打工人带3个孩子养4个父母月薪5千掏空6个钱包7天无休8十万买的房子不到9十平米生活10分困难36
--------------------------
{"id":38,"title":"关于生活38","content":"1对夫妻2个打工人带3个孩子养4个父母月薪5千掏空6个钱包7天无休8十万买的房子不到9十平米生活10分困难38"}
文档的属性:
id:38
title:关于生活38
content:1对夫妻2个打工人带3个孩子养4个父母月薪5千掏空6个钱包7天无休8十万买的房子不到9十平米生活10分困难38
--------------------------
{"id":54,"title":"关于生活54","content":"1对夫妻2个打工人带3个孩子养4个父母月薪5千掏空6个钱包7天无休8十万买的房子不到9十平米生活10分困难54"}
文档的属性:
id:54
title:关于生活54
content:1对夫妻2个打工人带3个孩子养4个父母月薪5千掏空6个钱包7天无休8十万买的房子不到9十平米生活10分困难54
--------------------------
{"id":55,"title":"关于生活55","content":"1对夫妻2个打工人带3个孩子养4个父母月薪5千掏空6个钱包7天无休8十万买的房子不到9十平米生活10分困难55"}
文档的属性:
id:55
title:关于生活55
content:1对夫妻2个打工人带3个孩子养4个父母月薪5千掏空6个钱包7天无休8十万买的房子不到9十平米生活10分困难55
--------------------------
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
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
# 查询结果高亮显示
在使用搜索引擎的过程中,关键词会被高亮显示:例如字体变为红色
Elasticsearch 也支持,配置步骤:
- 设置高亮显示的字段(Field),前缀和后缀(例如 HTML 标签中有个
<em>
,可以加粗显示字体) - 在 Clinet 对象执行查询之前,设置高亮显示的信息。
- 遍历结果列表时可以从结果中取高亮结果。
示例:
@Test
public void searchIndexWithHighLight() {
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("title");
highlightBuilder.preTags("<em>");
highlightBuilder.postTags("</em>");
QueryBuilder queryBuilder = QueryBuilders.queryStringQuery("梦想").defaultField("title");
SearchResponse searchResponse = client.prepareSearch("index_hello")
.setTypes("article")
.setQuery(queryBuilder)
.highlighter(highlightBuilder)
.get();
SearchHits searchHits = searchResponse.getHits();
System.out.println("查询结果总记录数:" + searchHits.getTotalHits());
for (SearchHit searchHit : searchHits) {
System.out.println("高亮结果: ");
Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
System.out.println(highlightFields);
// 遍历高亮字段
HighlightField highlightField = highlightFields.get("title");
Text[] fragments = highlightField.getFragments();
if(fragments != null){
String title = fragments[0].toString();
System.out.println("title: " + title);
}
}
}
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
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
运行结果:
查询结果总记录数:1
高亮结果:
{title=[title], fragments[[关于<em>梦想</em>]]}
title: 关于<em>梦想</em>
1
2
3
4
2
3
4
# 源码
本项目已将源码上传到 Gitee (opens new window) 和 GitHub (opens new window) 上。并且创建了分支 demo2,读者可以通过切换分支来查看本文的示例代码
上次更新: 2024/10/3 10:01:52