好玩的ES--第三篇之过滤查询,整合SpringBoot

x33g5p2x  于2022-03-05 转载在 Spring  
字(12.0k)|赞(0)|评价(0)|浏览(381)

好玩的ES—第一篇之安装和基本CRUD

好玩的ES–第二篇之高级查询,索引原理和分词器

过滤查询

过滤查询

过滤查询,其实准确来说,ES中的查询操作分为2种: 查询(query)过滤(filter)。查询即是之前提到的query查询,它 (查询)默认会计算每个返回文档的得分,然后根据得分排序。而过滤(filter)只会筛选出符合的文档,并不计算 得分,而且它可以缓存文档 。所以,单从性能考虑,过滤比查询更快。 换句话说过滤适合在大范围筛选数据,而查询则适合精确匹配数据。一般应用时, 应先使用过滤操作过滤数据, 然后使用查询匹配数据。

使用

  1. GET /ems/emp/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. {"match_all": {}} //查询条件
  7. ],
  8. "filter": {....} //过滤条件
  9. }
  10. }
  • 注意:

  • 在执行 filter 和 query 时,先执行 filter 在执行 query

  • Elasticsearch会自动缓存经常使用的过滤器,以加快性能。

类型

常见过滤类型有: term 、 terms 、ranage、exists、ids等filter。

term 、 terms Filter

  1. GET /ems/emp/_search # 使用term过滤
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. {"term": {
  7. "name": {
  8. "value": "小黑"
  9. }
  10. }}
  11. ],
  12. "filter": {
  13. "term": {
  14. "content":"框架"
  15. }
  16. }
  17. }
  18. }
  19. }
  20. GET /dangdang/book/_search #使用terms过滤
  21. {
  22. "query": {
  23. "bool": {
  24. "must": [
  25. {"term": {
  26. "name": {
  27. "value": "中国"
  28. }
  29. }}
  30. ],
  31. "filter": {
  32. "terms": {
  33. "content":[
  34. "科技",
  35. "声音"
  36. ]
  37. }
  38. }
  39. }
  40. }
  41. }

ranage filter

  1. GET /ems/emp/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. {"term": {
  7. "name": {
  8. "value": "中国"
  9. }
  10. }}
  11. ],
  12. "filter": {
  13. "range": {
  14. "age": {
  15. "gte": 7,
  16. "lte": 20
  17. }
  18. }
  19. }
  20. }
  21. }
  22. }

exists filter

过滤存在指定字段,获取字段不为空的索引记录使用

  1. GET /ems/emp/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. {"term": {
  7. "name": {
  8. "value": "中国"
  9. }
  10. }}
  11. ],
  12. "filter": {
  13. "exists": {
  14. "field":"aaa"
  15. }
  16. }
  17. }
  18. }
  19. }

ids filter

过滤含有指定字段的索引记录

  1. GET /ems/emp/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. {"term": {
  7. "name": {
  8. "value": "中国"
  9. }
  10. }}
  11. ],
  12. "filter": {
  13. "ids": {
  14. "values": ["1","2","3"]
  15. }
  16. }
  17. }
  18. }
  19. }

整合应用

引入依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
  4. </dependency>

配置客户端

  1. @Data
  2. @Configuration
  3. public class RestClientConfig extends AbstractElasticsearchConfiguration {
  4. @Value("${es.host}")
  5. private String ES_HOST;
  6. @Override
  7. @Bean
  8. public RestHighLevelClient elasticsearchClient() {
  9. final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
  10. .connectedTo(ES_HOST)
  11. .build();
  12. return RestClients.create(clientConfiguration).rest();
  13. }
  14. }

es默认开放了两个端口进行访问,一个是9200的rest方式访问,一个是9300的tcp方式访问,这里推荐使用9200rest方式的访问

客户端对象

  • ElasticsearchOperations
  • RestHighLevelClient 推荐

ElasticsearchOperations

  • 特点: 始终使用面向对象方式操作 ES

  • 索引: 用来存放相似文档集合

  • 映射: 用来决定放入文档的每个字段以什么样方式录入到 ES 中 字段类型 分词器…

  • 文档: 可以被索引最小单元 json 数据格式

相关注解

  1. @Data
  2. @Document(indexName = "products", createIndex = true)
  3. public class Product {
  4. @Id
  5. private Integer id;
  6. @Field(type = FieldType.Keyword)
  7. private String title;
  8. @Field(type = FieldType.Float)
  9. private Double price;
  10. @Field(type = FieldType.Text)
  11. private String description;
  12. }
  1. //1. @Document(indexName = "products", createIndex = true) 用在类上 作用:代表一个对象为一个文档
  2. -- indexName属性: 创建索引的名称
  3. -- createIndex属性: 是否创建索引
  4. //2. @Id 用在属性上 作用:将对象id字段与ES中文档的_id对应
  5. //3. @Field(type = FieldType.Keyword) 用在属性上 作用:用来描述属性在ES中存储类型以及分词情况
  6. -- type: 用来指定字段类型
索引文档
  1. @Test
  2. public void testCreate() throws IOException {
  3. Product product = new Product();
  4. product.setId(1); //存在id指定id 不存在id自动生成id
  5. product.setTitle("怡宝矿泉水");
  6. product.setPrice(129.11);
  7. product.setDescription("我们喜欢喝矿泉水....");
  8. //文档不存在会创建文档,文档存在会更新文档
  9. elasticsearchOperations.save(product);
  10. }
删除文档
  1. @Test
  2. public void testDelete() {
  3. Product product = new Product();
  4. product.setId(1);
  5. String delete = elasticsearchOperations.delete(product);
  6. System.out.println(delete);
  7. }
查询文档
  1. @Test
  2. public void testGet() {
  3. Product product = elasticsearchOperations.get("1", Product.class);
  4. System.out.println(product);
  5. }
更新文档
  1. @Test
  2. public void testUpdate() {
  3. Product product = new Product();
  4. product.setId(1);
  5. product.setTitle("怡宝矿泉水");
  6. product.setPrice(129.11);
  7. product.setDescription("我们喜欢喝矿泉水,你们喜欢吗....");
  8. elasticsearchOperations.save(product);//不存在添加,存在更新
  9. }
删除所有
  1. @Test
  2. public void testDeleteAll() {
  3. elasticsearchOperations.delete(Query.findAll(), Product.class);
  4. }
查询所有
  1. @Test
  2. public void testFindAll() {
  3. SearchHits<Product> productSearchHits = elasticsearchOperations.search(Query.findAll(), Product.class);
  4. productSearchHits.forEach(productSearchHit -> {
  5. System.out.println("id: " + productSearchHit.getId());
  6. System.out.println("score: " + productSearchHit.getScore());
  7. Product product = productSearchHit.getContent();
  8. System.out.println("product: " + product);
  9. });
  10. }

RestHighLevelClient

创建索引映射
  1. @Test
  2. public void testCreateIndex() throws IOException {
  3. CreateIndexRequest createIndexRequest = new CreateIndexRequest("fruit");
  4. createIndexRequest.mapping("{\n" +
  5. " \"properties\": {\n" +
  6. " \"title\":{\n" +
  7. " \"type\": \"keyword\"\n" +
  8. " },\n" +
  9. " \"price\":{\n" +
  10. " \"type\": \"double\"\n" +
  11. " },\n" +
  12. " \"created_at\":{\n" +
  13. " \"type\": \"date\"\n" +
  14. " },\n" +
  15. " \"description\":{\n" +
  16. " \"type\": \"text\"\n" +
  17. " }\n" +
  18. " }\n" +
  19. " }\n" , XContentType.JSON);
  20. CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
  21. System.out.println(createIndexResponse.isAcknowledged());
  22. restHighLevelClient.close();
  23. }

索引文档
  1. @Test
  2. public void testIndex() throws IOException {
  3. IndexRequest indexRequest = new IndexRequest("fruit");
  4. indexRequest.source("{\n" +
  5. " \"id\" : 1,\n" +
  6. " \"title\" : \"蓝月亮\",\n" +
  7. " \"price\" : 123.23,\n" +
  8. " \"description\" : \"这个洗衣液非常不错哦!\"\n" +
  9. " }",XContentType.JSON);
  10. IndexResponse index = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
  11. System.out.println(index.status());
  12. }
更新文档
  1. @Test
  2. public void testUpdate() throws IOException {
  3. UpdateRequest updateRequest = new UpdateRequest("fruit","qJ0R9XwBD3J1IW494-Om");
  4. updateRequest.doc("{\"title\":\"好月亮\"}",XContentType.JSON);
  5. UpdateResponse update = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
  6. System.out.println(update.status());
  7. }
删除文档
  1. @Test
  2. public void testDelete() throws IOException {
  3. DeleteRequest deleteRequest = new DeleteRequest("fruit","1");
  4. DeleteResponse delete = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
  5. System.out.println(delete.status());
  6. }
基于 id 查询文档
  1. @Test
  2. public void testGet() throws IOException {
  3. GetRequest getRequest = new GetRequest("fruit","aPbmV38BvtuRfHsTIvNo");
  4. GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
  5. System.out.println(getResponse.getSourceAsString());
  6. }
查询所有
  1. public void commonExampleSearch(String indice, QueryBuilder queryBuilder) throws IOException {
  2. SearchRequest searchRequest = new SearchRequest(indice);
  3. SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
  4. sourceBuilder.query(queryBuilder);
  5. searchRequest.source(sourceBuilder);
  6. SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  7. System.out.println("总记录数: "+searchResponse.getHits().getTotalHits().value);
  8. System.out.println("最大得分: "+searchResponse.getHits().getMaxScore());
  9. SearchHit[] hits = searchResponse.getHits().getHits();
  10. for (SearchHit hit : hits) {
  11. System.out.println(hit.getSourceAsString());
  12. }
  13. }
  14. @Test
  15. public void testSearch() throws IOException {
  16. String indice="fruit";
  17. //查询所有
  18. commonExampleSearch(indice,QueryBuilders.matchAllQuery());
  19. //term查询
  20. commonExampleSearch(indice,QueryBuilders.termQuery("description","不错哦!"));
  21. //prefix查询
  22. commonExampleSearch(indice,QueryBuilders.prefixQuery("description","这个"));
  23. //通配符查询
  24. commonExampleSearch(indice,QueryBuilders.wildcardQuery("title","好*"));
  25. //ids查询--多id查询
  26. commonExampleSearch(indice,QueryBuilders.idsQuery().addIds("1","2"));
  27. //多字段查询
  28. commonExampleSearch(indice,QueryBuilders.multiMatchQuery("不错","title","description"));
  29. }
综合查询
  1. @Test
  2. public void testSearch1() throws IOException {
  3. SearchRequest searchRequest = new SearchRequest("fruit");
  4. SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  5. sourceBuilder
  6. //分页查询
  7. .from(0)//起始位置 start=(page-1)*size
  8. .size(2)//每页显示条数,默认返回10条
  9. //指定排序字段,参数一:根据哪个字段进行排序,参数二:排序方式
  10. .sort("price", SortOrder.DESC)
  11. //返回的结果中排除或者包含哪些字段
  12. //参数1:包含的字段数组
  13. //参数2:排除字段数组
  14. .fetchSource(new String[]{"title"},new String[]{})
  15. //高亮设置
  16. .highlighter(new HighlightBuilder()
  17. //高亮显示的字段
  18. .field("description")
  19. //多字段高亮开启
  20. .requireFieldMatch(false)
  21. //自定义高亮html标签
  22. .preTags("<span style='color:red;'>").postTags("</span>"))
  23. //查询
  24. .query(QueryBuilders.termQuery("description","错"));
  25. searchRequest.source(sourceBuilder);
  26. SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  27. System.out.println("总条数: "+searchResponse.getHits().getTotalHits().value);
  28. SearchHit[] hits = searchResponse.getHits().getHits();
  29. for (SearchHit hit : hits) {
  30. System.out.println(hit.getSourceAsString());
  31. //显示当前查询结果中出现的高亮字段
  32. Map<String, HighlightField> highlightFields = hit.getHighlightFields();
  33. highlightFields.forEach((k,v)-> System.out.println("key: "+k + " value: "+v.fragments()[0]));
  34. }
  35. }
过滤查询
  1. /**
  2. * query: 精确查询,查询计算文档得分,并根据文档得分进行返回
  3. * filter query: 过滤查询,用来在大量数据中筛选出本地查询相关数据,不会计算文档得分,经常使用filter query结果进行缓存
  4. * 注意: 一旦使用query和filterQuery es优先执行filter query 然后再执行 query
  5. */
  6. @Test
  7. public void testFilterQuery() throws IOException {
  8. SearchRequest searchRequest=new SearchRequest("fruit");
  9. SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
  10. sourceBuilder.query(QueryBuilders.termQuery("description","不错"))
  11. //指定过滤条件
  12. .postFilter(QueryBuilders.idsQuery().addIds("1","2","3"));
  13. searchRequest.source(sourceBuilder);
  14. SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  15. System.out.println("符合条件的总数为: "+searchResponse.getHits().getTotalHits().value);
  16. }
思路扩展

ElasticsearchOperations面向对象的查询方式,有其优点所在,那么我们能否将其和RestHighLevelClient 进行互补呢 ?

看下面的例子:

  1. @AllArgsConstructor
  2. @NoArgsConstructor
  3. @Builder
  4. @Data
  5. public class Fruit implements Serializable {
  6. private String title;
  7. private Double price;
  8. private Date create_at;
  9. private String description;
  10. }
  1. /**
  2. * @author 大忽悠
  3. * @create 2022/3/5 11:34
  4. */
  5. public class AllTest extends EsApplicationTests{
  6. ObjectMapper objectMapper=new ObjectMapper();
  7. /**
  8. * 添加文档
  9. */
  10. @Test
  11. public void addIndice() throws IOException {
  12. Fruit fruit = Fruit.builder().id(5).title("大忽悠").price(520.521)
  13. .description("大忽悠喜欢小朋友")
  14. .build();
  15. IndexRequest indexRequest=new IndexRequest("fruit");
  16. indexRequest.id(fruit.getId().toString()).source(objectMapper.writeValueAsString(fruit),XContentType.JSON);
  17. IndexResponse index = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
  18. System.out.println(index.status());
  19. }
  20. public void commonExampleSearch(String indice, QueryBuilder queryBuilder) throws IOException {
  21. SearchRequest searchRequest = new SearchRequest(indice);
  22. SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
  23. sourceBuilder.query(queryBuilder);
  24. searchRequest.source(sourceBuilder);
  25. SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  26. System.out.println("总记录数: "+searchResponse.getHits().getTotalHits().value);
  27. System.out.println("最大得分: "+searchResponse.getHits().getMaxScore());
  28. SearchHit[] hits = searchResponse.getHits().getHits();
  29. for (SearchHit hit : hits) {
  30. System.out.println("返回的结果为: "+hit.getSourceAsString());
  31. //JSON格式转换
  32. Fruit fruit = objectMapper.readValue(hit.getSourceAsString(), Fruit.class);
  33. System.out.println("得到的结果为: "+fruit);
  34. }
  35. }
  36. /**
  37. * 查询全部
  38. */
  39. @Test
  40. public void searchAll() throws IOException {
  41. String indice="fruit";
  42. //查询所有
  43. commonExampleSearch(indice, QueryBuilders.matchAllQuery());
  44. }
  45. }

注意: 两者的区别,因此我们在上传文档的时候,一定要通过objectMapper.writeValueAsString(fruit)的方式先转换为JSON串的原因

  1. Fruit fruit = Fruit.builder().id(5).title("大忽悠").price(520.521)
  2. .description("大忽悠喜欢小朋友")
  3. .build();
  4. System.out.println(objectMapper.writeValueAsString(fruit));
  5. System.out.println(fruit);

相关文章