【ElasticSearch】02-ElasticSearch使用

cover-02

ElasticSearch和SpringBoot

ElasticSearch和SpringBoot联合使用,可以使用Java原生提供的RestHighLevelClient进行ElasticSearch的所有操作。
也可以使用CrudRepository对ElasticSearch执行简单的增删改查操作。

项目项目

img-36

  1. pom.xml

    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
    <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <elasticsearch.version>7.15.2</elasticsearch.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>compile</scope>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.5.8</version>
    </dependency>
    </dependencies>
  2. application.yml

    1
    2
    3
    4
    5
    6
    7
    8
    server:
    port: 12051
    spring:
    application:
    name: springboot-elasticsearch
    elasticsearch:
    rest:
    uris: api.es.com:9200
  3. ElasticSearchConfig.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Configuration
    public class ElasticSearchConfig {

    @Bean
    public RestHighLevelClient getRestHighLevelClient() {
    return new RestHighLevelClient(RestClient.builder(new HttpHost("api.es.com", 9200, "http")));
    }

    }
  4. ElasticSearchApplication.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /**
    * 配置文件、配置类中的ElasticSearch链接配置其中一个即可
    */
    @SpringBootApplication
    public class ElasticSearchApplication {

    public static void main(String[] args) {
    SpringApplication.run(ElasticSearchApplication.class);
    }

    }
  5. UserSalary.java

    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
    @Data
    @Document(indexName = "user_salary")
    public class UserSalary {

    /**
    * id
    */
    @Id
    private String id;
    /**
    * 姓名
    */
    private String name;
    /**
    * 职位
    */
    private String deployment;
    /**
    * 薪水
    */
    private Integer salary;
    /**
    * 创建时间
    */
    private Long createTime;
    /**
    * 更新时间
    */
    private Long updateTime;

    }
  6. SnowflakesUtil.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class SnowflakesUtil {

    private static final Long WORKER_ID = 1L;
    private static final Long DATACENTER_ID = 1L;

    public static Long getNextId() {
    return IdUtil.getSnowflake(WORKER_ID, DATACENTER_ID).nextId();
    }

    }

操作索引

IndexServiceImpl.java

1
2
3
4
5
6
7
8
@Slf4j
@Service
public class IndexServiceImpl implements IndexService {

@Resource
private RestHighLevelClient client;

}

创建索引

1
2
3
4
5
6
public void createIndex(String index) throws IOException {
log.info("index = {}", index);
CreateIndexRequest request = new CreateIndexRequest(index);
CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
log.info("response.isAcknowledged() = {}", response.isAcknowledged());
}

删除索引

1
2
3
4
5
6
public void deleteIndex(String index) throws IOException {
log.info("index = {}", index);
DeleteIndexRequest request = new DeleteIndexRequest(index);
AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
log.info("response.isAcknowledged() = {}", response.isAcknowledged());
}

查询索引

1
2
3
4
5
public void searchIndex(String index) throws IOException {
GetIndexRequest request = new GetIndexRequest(index);
GetIndexResponse response = client.indices().get(request, RequestOptions.DEFAULT);
log.info("response.getAliases() = {}", response.getAliases());
}

操作文档

DocumentServiceImpl.java

1
2
3
4
5
6
7
8
@Slf4j
@Service
public class DocumentServiceImpl implements DocumentService {

@Resource
private RestHighLevelClient client;

}

创建文档

1
2
3
4
5
6
7
8
9
10
11
public void create(String index, UserSalary userSalary) throws IOException {
String id = String.valueOf(SnowflakesUtil.getNextId());
userSalary.setCreateTime(System.currentTimeMillis());
userSalary.setUpdateTime(System.currentTimeMillis());
userSalary.setId(id);
IndexRequest request = new IndexRequest();
request.index(index).id(id);
request.source(BeanUtil.beanToMap(userSalary));
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
System.out.println("_result:" + response.getResult());
}

批量创建文档

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
public void createBatch(String index) throws IOException {
BulkRequest request = new BulkRequest();
List<UserSalary> list = getUserSalaryList();

for (UserSalary userSalary : list) {
String str = JSONUtil.toJsonStr(userSalary);
// 下面的.source()中的参数,有两种写法,这里是其中一种。
request.add(new IndexRequest().index(index).id(userSalary.getId()).source(str, XContentType.JSON));
}
BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
System.out.println("took = " + response.getTook());
System.out.println("items = " + ArrayUtil.join(response.getItems(), ", "));
}

private List<UserSalary> getUserSalaryList() {
List<UserSalary> list = CollUtil.newArrayList();
long now = System.currentTimeMillis();

UserSalary userSalary1 = new UserSalary();
userSalary1.setId(String.valueOf(SnowflakesUtil.getNextId()));
userSalary1.setName("蒋一一");
userSalary1.setDeployment("客服");
userSalary1.setSalary(800000);
userSalary1.setCreateTime(now);
userSalary1.setUpdateTime(now);

UserSalary userSalary2 = new UserSalary();
userSalary2.setId(String.valueOf(SnowflakesUtil.getNextId()));
userSalary2.setName("沈阳");
userSalary2.setDeployment("客服");
userSalary2.setSalary(900000);
userSalary2.setCreateTime(now);
userSalary2.setUpdateTime(now);

UserSalary userSalary3 = new UserSalary();
userSalary3.setId(String.valueOf(SnowflakesUtil.getNextId()));
userSalary3.setName("韩金龙");
userSalary3.setDeployment("老板");
userSalary3.setSalary(100000000);
userSalary3.setCreateTime(now);
userSalary3.setUpdateTime(now);

list.add(userSalary1);
list.add(userSalary2);
list.add(userSalary3);
return list;
}

修改文档

1
2
3
4
5
6
7
8
9
10
11
12
13
public void update(String index, UserSalary userSalary) throws IOException {
Long now = System.currentTimeMillis();
userSalary.setUpdateTime(now);
UpdateRequest request = new UpdateRequest();
request.id(userSalary.getId());
request.index(index);
Map<String, Object> map = BeanUtil.beanToMap(userSalary);
request.doc(map);
// request.docAsUpsert():true-数据不存在则新增;false-数据不存在则忽略
request.docAsUpsert(true);
UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
System.out.println("_result:" + response.getResult());
}

删除文档

1
2
3
4
5
public void delete(String index, String id) throws IOException {
DeleteRequest request = new DeleteRequest().index(index).id(id);
DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
System.out.println("response = " + response.toString());
}

查询文档

查询文档的操作有很多:条件、分页、排序、组合条件(and, or)、通配符、范围、正则表达式、高亮、最大最小平均值、分组查询等

查询所有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public List<UserSalary> query() throws IOException {
String index = "user_salary";
List<UserSalary> list = CollUtil.newArrayList();
SearchRequest request = new SearchRequest(index);
SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource();
sourceBuilder.query(QueryBuilders.matchAllQuery());
System.out.println("【查询操作】 - 【查询语句】:" + sourceBuilder.toString());
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> {
Map<String, Object> map = hit.getSourceAsMap();
System.out.println("【查询结果】 - hit = " + map.toString());
UserSalary userSalary = BeanUtil.toBeanIgnoreError(map, UserSalary.class);
list.add(userSalary);
});
return list;
}

组合查询

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
/**
* 相当于下面的SQL语句
* select *
* from user_salary
* where (deployment like 'Java%' and salary >= 1300000)
* or (deployment like '测试%' and salary >= 1000000)
*/
public List<UserSalary> query2() throws IOException {
List<UserSalary> list = CollUtil.newArrayList();
SearchRequest request = new SearchRequest();
SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource();
BoolQueryBuilder java = new BoolQueryBuilder();
BoolQueryBuilder test = new BoolQueryBuilder();
BoolQueryBuilder should = new BoolQueryBuilder();
// 1. Java 13000
MatchQueryBuilder matchJava = QueryBuilders.matchQuery("deployment", "Java");
RangeQueryBuilder rangeJava = QueryBuilders.rangeQuery("salary").gte(1300000);
java.must(matchJava).must(rangeJava);
// 2. 测试 10000
MatchQueryBuilder matchTest = QueryBuilders.matchQuery("deployment", "测试");
RangeQueryBuilder rangeTest = QueryBuilders.rangeQuery("salary").gte(1000000);
test.must(matchTest).must(rangeTest);
// 3. 取1和2的并集
should.should(java).should(test);
sourceBuilder.query(should);
System.out.println("【查询操作(2)】 - 【查询语句】:" + sourceBuilder.toString());
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> {
Map<String, Object> map = hit.getSourceAsMap();
System.out.println("【查询操作(2)】 - 【结果】 - hit = " + map.toString());
UserSalary userSalary = BeanUtil.toBeanIgnoreError(map, UserSalary.class);
list.add(userSalary);
});
return list;
}

通配符查询

* : 相当于0个或多个字符
? : 相当于1个字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public List<UserSalary> query3() throws IOException {
List<UserSalary> list = CollUtil.newArrayList();
SearchRequest request = new SearchRequest();
SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource();
// wildcard: 1. * 匹配一个或多个字符;2. ?进匹配一个字符
sourceBuilder.query(QueryBuilders.wildcardQuery("deployment", "*程*"));
System.out.println("【查询操作(3)】 - 【查询语句】:" + sourceBuilder.toString());
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> {
Map<String, Object> map = hit.getSourceAsMap();
System.out.println("【查询操作(3)】 - 【结果】 - hit = " + map.toString());
UserSalary userSalary = BeanUtil.toBeanIgnoreError(map, UserSalary.class);
list.add(userSalary);
});
return list;
}

多值查询

相当于SQL中的in操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public List<UserSalary> query4() throws IOException {
List<UserSalary> list = CollUtil.newArrayList();
SearchRequest request = new SearchRequest();
SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource();
sourceBuilder.query(QueryBuilders.termsQuery("id", "1470290808988110848", "1470282535723470848"));
System.out.println("【查询操作(4)】 - 【查询语句】:" + sourceBuilder.toString());
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> {
Map<String, Object> map = hit.getSourceAsMap();
System.out.println("【查询操作(4)】 - 【结果】 - hit = " + map.toString());
UserSalary userSalary = BeanUtil.toBeanIgnoreError(map, UserSalary.class);
list.add(userSalary);
});
return list;
}

存在查询

相当于SQL中的exist查询操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public List<UserSalary> query5() throws IOException {
List<UserSalary> list = CollUtil.newArrayList();
String index = "user_salary";
SearchRequest request = new SearchRequest(index);
SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource();
sourceBuilder.query(QueryBuilders.existsQuery("salary"));
System.out.println("【查询操作(5)】 - 【查询语句】:" + sourceBuilder.toString());
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> {
Map<String, Object> map = hit.getSourceAsMap();
System.out.println("【查询操作(5)】 - 【结果】 - hit = " + map.toString());
UserSalary userSalary = BeanUtil.toBeanIgnoreError(map, UserSalary.class);
list.add(userSalary);
});
return list;
}

范围查询

相当于SQL中的between

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public List<UserSalary> query6() throws IOException {
List<UserSalary> list = CollUtil.newArrayList();
SearchRequest request = new SearchRequest();
SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource();
sourceBuilder.query(QueryBuilders.rangeQuery("createTime")
.gte(1639381811162L).lte(1639448670000L));
sourceBuilder.sort("createTime", SortOrder.DESC);
System.out.println("【查询操作(6)】 - 【查询语句】:" + sourceBuilder.toString());
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> {
Map<String, Object> map = hit.getSourceAsMap();
System.out.println("【查询操作(6)】 - 【结果】 - hit = " + map.toString());
UserSalary userSalary = BeanUtil.toBeanIgnoreError(map, UserSalary.class);
list.add(userSalary);
});
return list;
}

正则查询

(有问题,但是直接在kibana中查询没问题)

更复杂的通配符查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public List<UserSalary> query7() throws IOException {
List<UserSalary> list = CollUtil.newArrayList();
String index = "user_salary";
SearchRequest request = new SearchRequest(index);
SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource();
sourceBuilder.query(QueryBuilders.regexpQuery("deployment", "[a-zA-Z]{4}开发工程师"));
System.out.println("【查询操作(7)】 - 【查询语句】:" + sourceBuilder.toString());
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> {
Map<String, Object> map = hit.getSourceAsMap();
System.out.println("【查询操作(7)】 - 【结果】 - hit = " + map.toString());
UserSalary userSalary = BeanUtil.toBeanIgnoreError(map, UserSalary.class);
list.add(userSalary);
});
return list;
}

模糊查询

fuzziness模糊查询操作,我也没看这是干啥的,用一下这个也没看懂这里面的一个参数是干啥的Fuzziness.ZERO, Fuzziness.ONE, Fuzziness.TWO, Fuzziness.AUTO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public List<UserSalary> query8() throws IOException {
List<UserSalary> list = CollUtil.newArrayList();
SearchRequest request = new SearchRequest();
// 设置搜索的索引名称
request.indices("user_salary");

SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource();
sourceBuilder.query(QueryBuilders.fuzzyQuery("deployment", "开发工程师").
fuzziness(Fuzziness.ONE));
System.out.println("【查询操作(8)】 - 【查询语句】:" + sourceBuilder.toString());
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> {
Map<String, Object> map = hit.getSourceAsMap();
System.out.println("【查询操作(8)】 - 【结果】 - hit = " + map.toString());
UserSalary userSalary = BeanUtil.toBeanIgnoreError(map, UserSalary.class);
list.add(userSalary);
});
return list;
}

高亮查询

在查询结果的字段中加入高亮的html标签,该标签可自定义

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
public List<UserSalary> query9() throws IOException {
List<UserSalary> list = CollUtil.newArrayList();
String index = "user_salary";
SearchRequest request = new SearchRequest().indices(index);
// 高亮配置
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.preTags("<font color='red'>");
highlightBuilder.postTags("</font>");
highlightBuilder.field("deployment");
SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource();
sourceBuilder.highlighter(highlightBuilder);
sourceBuilder.query(QueryBuilders.matchQuery("deployment", "Java和前端"));
System.out.println("【查询操作(9)】 - 【查询语句】:" + sourceBuilder.toString());
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> {
Map<String, Object> map = hit.getSourceAsMap();
System.out.println("【查询操作(9)】 - 【结果】 - hit = " + map.toString());
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
HighlightField deployment = highlightFields.get("deployment");
UserSalary userSalary = BeanUtil.toBeanIgnoreError(map, UserSalary.class);
System.out.println(deployment.fragments()[0]);
System.out.println("StrUtil.toString(deployment.fragments()) = " + StrUtil.toString(deployment.fragments()));
userSalary.setDeployment(deployment.getFragments()[0].toString());
list.add(userSalary);
});
return list;
}

最高值查询

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
/**
* 计算薪水的最高值、最低值以及平均值
*/
public JSONObject query10() throws IOException {
String index = "user_salary";
SearchRequest request = new SearchRequest(index);
SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource();
sourceBuilder.aggregation(AggregationBuilders.max("maxSalary").field("salary"));
sourceBuilder.aggregation(AggregationBuilders.min("minSalary").field("salary"));
sourceBuilder.aggregation(AggregationBuilders.avg("avgSalary").field("salary"));
// size 不设置为0,会将存储的数据也查询出来。
sourceBuilder.size(0);
System.out.println("【查询操作(10)】 - 【查询语句】:" + sourceBuilder.toString());
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
System.out.println("【查询操作(10)】 - 【查询结果】:" + response.toString());

Map<String, Aggregation> map = response.getAggregations().getAsMap();

Aggregation maxSalary = map.get("maxSalary");
JSONObject maxSalaryJson = JSONUtil.parseObj(maxSalary);
String maxSalaryName = maxSalaryJson.getStr("name");
Long maxSalaryValue = maxSalaryJson.getLong("value");
System.out.println("maxSalary : name = " + maxSalaryName + ", value = " + maxSalaryValue);

Aggregation minSalary = map.get("minSalary");
JSONObject minSalaryJson = JSONUtil.parseObj(minSalary);
String minSalaryName = minSalaryJson.getStr("name");
Long minSalaryValue = minSalaryJson.getLong("value");
System.out.println("minSalary : name = " + minSalaryName + ", value = " + minSalaryValue);

Aggregation avgSalary = map.get("avgSalary");
JSONObject avgSalaryJson = JSONUtil.parseObj(avgSalary);
String avgSalaryName = avgSalaryJson.getStr("name");
Long avgSalaryValue = avgSalaryJson.getLong("value");
System.out.println("avgSalary : name = " + avgSalaryName + ", value = " + avgSalaryValue);

JSONObject result = new JSONObject();
result.set(maxSalaryName, maxSalaryValue);
result.set(minSalaryName, minSalaryValue);
result.set(avgSalaryName, avgSalaryValue);

return result;
}

分组查询

分组查询,获取每一组的文档数量

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
/**
* 分组查询不同薪水的人数
*/
public JSONObject query11() throws IOException {
String index = "user_salary";
SearchRequest request = new SearchRequest(index);
SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource();
sourceBuilder.aggregation(AggregationBuilders.terms("salary_group").field("salary"));
sourceBuilder.size(0);
log.info("【查询操作(11)】 - 【查询语句】:{}", sourceBuilder.toString());
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
System.out.println("【查询操作(11)】 - 【查询结果】:" + response.toString());
System.out.println(response.toString());
Map<String, Aggregation> map = response.getAggregations().getAsMap();
System.out.println(JSONUtil.toJsonStr(map));
Aggregation salaryGroup = map.get("salary_group");
JSONObject salaryGroupJson = JSONUtil.parseObj(salaryGroup);
JSONArray buckets = salaryGroupJson.getJSONArray("buckets");
System.out.println(buckets.toString());

JSONObject result = new JSONObject();
for (int i = 0; i < buckets.size(); i++) {
JSONObject bucket = buckets.getJSONObject(i);
Long key = bucket.getLong("key");
Integer count = bucket.getInt("docCount");
result.set(String.valueOf(key), count);
}
return result;
}

Repository操作文档

Repository进行增删改查应该都很熟悉了:save, delete, findById……等方法比较常用。其中仅提供了比较基础的增删改查方法,更复杂的查询方法,还是需要用RestHighLevelClient去完成。

-------------本文结束感谢您的阅读-------------