• ES客户端(RestHighLevelClient、SpringDataElasticsearch 框架)使用指南


    RestHighLevelClient

    客户端介绍

    elasticsearch 官网中提供了各种语言的客户端:

    在这里插入图片描述

    选择 Java REST Client

    选择 Java High Level Rest Client 版本,这里有使用的API

    在这里插入图片描述


    es依赖

        <dependency>
            <groupId>org.elasticsearchgroupId>
            <artifactId>elasticsearchartifactId>
            <version>6.2.4version>
        dependency>
        <dependency>
            <groupId>org.elasticsearch.clientgroupId>
            <artifactId>elasticsearch-rest-clientartifactId>
            <version>6.2.4version>
        dependency>
        <dependency>
            <groupId>org.elasticsearch.clientgroupId>
            <artifactId>elasticsearch-rest-high-level-clientartifactId>
            <version>6.2.4version>
        dependency>
    
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.70version>
        dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    索引库及映射

    创建索引库的同时创建type及其映射关系,但是这些操作不建议使用java客户端完成,原因如下:

    • 索引库和映射往往是初始化时完成,不需要频繁操作,不如提前配置好

    • 官方提供的创建索引库及映射API非常繁琐,需要通过字符串拼接json结构:

      request.mapping(
              "{\n" +
              "  \"properties\": {\n" +
              "    \"message\": {\n" +
              "      \"type\": \"text\"\n" +
              "    }\n" +
              "  }\n" +
              "}", 
              XContentType.JSON);
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

    因此,这些操作建议还是使用 Rest 风格API去实现。

    以这样一个商品数据为例来创建索引库:

    新增实体类:

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Item {
        private Long id;
        private String title; //标题
        private String category;// 分类
        private String brand; // 品牌
        private Double price; // 价格
        private String images; // 图片地址
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • id:可以认为是主键,将来判断数据是否重复的标示,不分词,可以使用keyword类型
    • title:搜索字段,需要分词,可以用text类型
    • category:商品分类,这个是整体,不分词,可以使用keyword类型
    • brand:品牌,与分类类似,不分词,可以使用keyword类型
    • price:价格,这个是double类型
    • images:图片,用来展示的字段,不搜索,index为false,不分词,可以使用keyword类型

    映射配置:

    PUT /item
    {
      "settings": {
        "number_of_shards": 3,
        "number_of_replicas": 1
      },
      "mappings": {
        "_doc": {
          "properties": {
            "id": {
              "type": "keyword"
            },
            "title": {
              "type": "text",
              "analyzer": "ik_max_word"
            },
            "category": {
              "type": "keyword"
            },
            "brand": {
              "type": "keyword"
            },
            "images": {
              "type": "keyword",
              "index": false
            },
            "price": {
              "type": "double"
            }
          }
        }
      }
    }
    
    • 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

    查看添加结果

    在这里插入图片描述


    文档操作

    初始化http客户端:RestHighLevelClient

    完成任何操作都需要通过 RestHighLevelClient 客户端

    入门示例

    @RunWith(SpringRunner.class)
    @SpringBootTest
    class EsDemoApplicationTests {
    
        RestHighLevelClient client;
    
        /**
         * 初始化连接
         */
        @Before
        void init() {
            //初始化:高级客户端
            client = new RestHighLevelClient(RestClient.builder(
                new HttpHost("192.168.85.135", 9201, "http"),
                new HttpHost("192.168.85.135", 9202, "http"),
                new HttpHost("192.168.85.135", 9203, "http")
            ));
        }
        
        @After
        void close() throws IOException {
            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

    应用级案例

    application.yml 配置文件

    # es集群名称
    elasticsearch.clusterName=single-node-cluster
    # es用户名
    elasticsearch.userName=elastic
    # es密码
    elasticsearch.password=elastic
    # es 是否启用用户密码
    elasticsearch.passwdEnabled=true
    # es host ip 地址(集群):本次使用的是单机模式
    elasticsearch.hosts=43.142.243.124:9200
    # es 请求方式
    elasticsearch.scheme=http
    # es 连接超时时间
    elasticsearch.connectTimeOut=1000
    # es socket 连接超时时间
    elasticsearch.socketTimeOut=30000
    # es 请求超时时间
    elasticsearch.connectionRequestTimeOut=500
    # es 连接保持活跃时间(ms)
    elasticsearch.keepAliveStrategy=180000
    # es 最大连接数
    elasticsearch.maxConnectNum=100
    # es 每个路由的最大连接数
    elasticsearch.maxConnectNumPerRoute=100
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    es连接配置类

    import lombok.Data;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.http.HttpHost;
    import org.apache.http.HttpRequest;
    import org.apache.http.HttpResponse;
    import org.apache.http.auth.AuthScope;
    import org.apache.http.auth.UsernamePasswordCredentials;
    import org.apache.http.client.CredentialsProvider;
    import org.apache.http.impl.client.BasicCredentialsProvider;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestClientBuilder;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * restHighLevelClient 客户端配置类
     */
    @Slf4j
    @Data
    @Configuration
    @ConfigurationProperties(prefix = "elasticsearch")
    public class ElasticsearchConfig {
    
        // es host ip 地址(集群)
        private String hosts;
        // es用户名
        private String userName;
        // es密码
        private String password;
        // es 是否启用用户密码
        private boolean passwdEnabled;
        // es 请求方式
        private String scheme;
        // es集群名称
        private String clusterName;
        // es 连接超时时间
        private int connectTimeOut;
        // es socket 连接超时时间
        private int socketTimeOut;
        // es 请求超时时间
        private int connectionRequestTimeOut;
        // es 连接保持活跃时间
        private int keepAliveStrategy;
        // es 最大连接数
        private int maxConnectNum;
        // es 每个路由的最大连接数
        private int maxConnectNumPerRoute;
    
        @Bean(name = "restHighLevelClient")
        public RestHighLevelClient restHighLevelClient() {
            // 拆分地址。单节点配一个地址即可
            List<HttpHost> hostLists = new ArrayList<>();
            hosts = hosts.replace("http://", "");
            String[] hostList = hosts.split(",");
            for (String addr : hostList) {
                String host = addr.split(":")[0];
                String port = addr.split(":")[1] == null ? "9200": addr.split(":")[1];
                hostLists.add(new HttpHost(host, Integer.parseInt(port), scheme));
            }
            // 转换成 HttpHost 数组
            HttpHost[] httpHost = hostLists.toArray(new HttpHost[]{});
    
            // 构建连接对象
            RestClientBuilder builder = RestClient.builder(httpHost);
    
            // 连接延时配置
            builder.setRequestConfigCallback(requestConfigBuilder -> {
                requestConfigBuilder
                    .setConnectTimeout(connectTimeOut)
                    .setSocketTimeout(socketTimeOut)
                    .setConnectionRequestTimeout(connectionRequestTimeOut);
                return requestConfigBuilder;
            });
    
    
            builder.setHttpClientConfigCallback(httpClientBuilder -> {
                httpClientBuilder
                    // 连接数配置
                    .setMaxConnTotal(maxConnectNum)
                    .setMaxConnPerRoute(maxConnectNumPerRoute)
                    // 连接保持活跃时间配置
                    .setKeepAliveStrategy((HttpRequest, HttpResponse) -> keepAliveStrategy);
                // 设置用户名、密码
                if (passwdEnabled) {
                    CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
                    credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(userName, password));
                    httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
                }
                return httpClientBuilder;
            });
    
            return new RestHighLevelClient(builder);
        }
    }
    
    • 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
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99

    注:

    • KeepAliveStrategy :

      HTTP 规范没有确定一个持久连接可能或应该保持活动多长时间。

      一些HTTP服务器使用非标准的头部信息 Keep-Alive 来告诉客户端它们想在服务器端保持连接活动的周期秒数。

      如果这个信息可用,HttpClient 就会利用这个它。

      如果头部信息 Keep-Alive 在响应中不存在,HttpClient 假设连接无限期的保持活动。

      然而许多现实中的 HTTP 服务器配置了在特定不活动周期之后丢掉持久连接来保存系统资源,往往这是不通知客户端的。


    创建索引及映射

    索引的 mappings

    mapping_test.json

    {
        "properties": {
            "brandName": {
                "type": "keyword"
            }, 
            "categoryName": {
                "type": "keyword"
            }, 
            "createTime": {
                "type": "date", 
                "format": "yyyy-MM-dd HH:mm:ss"
            }, 
            "id": {
                "type": "long"
            }, 
            "price": {
                "type": "double"
            }, 
            "saleNum": {
                "type": "integer"
            }, 
            "status": {
                "type": "integer"
            }, 
            "stock": {
                "type": "integer"
            }, 
            "spec": {
                "type": "text", 
                "analyzer": "ik_max_word", 
                "search_analyzer": "ik_smart"
            }, 
            "title": {
                "type": "text", 
                "analyzer": "ik_max_word", 
                "search_analyzer": "ik_smart"
            }
        }
    }
    
    • 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
    import com.example.test.service.es.IndexTestService;
    import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
    import org.elasticsearch.action.support.master.AcknowledgedResponse;
    import org.elasticsearch.client.IndicesClient;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.client.indices.CreateIndexRequest;
    import org.elasticsearch.client.indices.CreateIndexResponse;
    import org.elasticsearch.client.indices.GetIndexRequest;
    import org.elasticsearch.client.indices.GetIndexResponse;
    import org.elasticsearch.cluster.metadata.MappingMetaData;
    import org.elasticsearch.common.xcontent.XContentType;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.Map;
    
    /**
     * 索引服务类
     */
    @Service
    public class IndexTestServiceImpl implements IndexTestService {
        
        @value("classpath:json/mapping_test.json")
        private resoure mappingTest;
    
        @Autowired
        RestHighLevelClient restHighLevelClient;
        
        // 分片数的配置名
        private String shardNumName = "number_of_shards";
        
        // 副本数的配置名
        private String replicaNumName = "number_of_replicas";
        
        // 索引名
        private String index = "goods"
    
        @Override
        public boolean indexCreate() throws Exception {
            // 1、创建 创建索引request 参数:索引名
            CreateIndexRequest indexRequest = new CreateIndexRequest(index);
            // 2、设置索引的settings
            indexRequest.settings(Settings.builder().put(shardNumName, 3).put(replicaNumName, 1));
            // 3、设置索引的mappings(表结构)
            String mappingJson = IOUtils.toString(mappingTest.getInputStream(), Charset.forName("UTF-8"));
            indexRequest.mapping(mappingJson, XContentType.JSON);
            // 4、 设置索引的别名
            // 5、 发送请求
            // 5.1 同步方式发送请求
            // 请求服务器
            IndicesClient indicesClient = restHighLevelClient.indices();
            CreateIndexResponse response = indicesClient.create(indexRequest, RequestOptions.DEFAULT);
    
            return response.isAcknowledged();
        }
    
    
        /**
         * 获取表结构
         * GET goods/_mapping
         */
        @Override
        public Map<String, Object> getMapping(String indexName) throws Exception {
            IndicesClient indicesClient = restHighLevelClient.indices();
    
            // 创建get请求
            GetIndexRequest request = new GetIndexRequest(indexName);
            // 发送get请求
            GetIndexResponse response = indicesClient.get(request, RequestOptions.DEFAULT);
            // 获取表结构
            Map<String, MappingMetaData> mappings = response.getMappings();
            Map<String, Object> sourceAsMap = mappings.get(indexName).getSourceAsMap();
            return sourceAsMap;
        }
    
        /**
         * 删除索引库
         */
        @Override
        public boolean indexDelete(String indexName) throws Exception {
            IndicesClient indicesClient = restHighLevelClient.indices();
            // 创建delete请求方式
            DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indexName);
            // 发送delete请求
            AcknowledgedResponse response = indicesClient.delete(deleteIndexRequest, RequestOptions.DEFAULT);
    
            return response.isAcknowledged();
        }
    
        /**
         * 判断索引库是否存在
         */
        @Override
        public boolean indexExists(String indexName) throws Exception {
            IndicesClient indicesClient = restHighLevelClient.indices();
            // 创建get请求
            GetIndexRequest request = new GetIndexRequest(indexName);
            // 判断索引库是否存在
            boolean result = indicesClient.exists(request, RequestOptions.DEFAULT);
    
            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
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104

    新增文档:index

    文档:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/6.8/java-rest-high-document-index.html

    示例:

    // 新增文档
    @Test
    void add() throws IOException {
        // 准备文档
        Item item = new Item(1L, "小米手机9", "手机",
                "小米", 3499.00, "http://image.leyou.com/13123.jpg");
        // 将对象转换为Json
        String json = JSON.toJSONString(item);
        // 创建索引请求 参数为: 索引库名   类型名  文档ID
        IndexRequest request = new IndexRequest("item", "_doc", item.getId().toString());
        // 将Json格式的数据放入到请求中
        request.source(json, XContentType.JSON);
        // 发送请求
        IndexResponse response = client.index(request);
        // 打印结果
        System.out.println("结果为:" + response);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    响应:

    response = IndexResponse[index=item,type=docs,id=1,version=1,result=created,seqNo=0,primaryTerm=1,shards={"total":2,"successful":2,"failed":0}]
    
    • 1

    查看文档:get

    示例:

    // 根据ID获取文档
    @Test
    void get() throws IOException {
        // 创建get请求对象 参数为: 索引库名   类型名  文档ID
        GetRequest request = new GetRequest("item","_doc","1");
        // 发送请求
        GetResponse response = client.get(request);
        // 解析结果 结果为Json
        String source = response.getSourceAsString();
        // 将Json数据转换为对象 参数为: Json字符串  类的字节码
        Item item = JSON.parseObject(source, Item.class);
        // 打印结果
        System.out.println(item);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    更新文档:update

    示例:

    // 根据ID更新文档
    @Test
    void update() throws IOException{
        // 准备文档
        Item item = new Item(1L, "小米手机9", "手机",
                "小米", 3699.00, "http://image.leyou.com/13123.jpg");
        // 将对象转换为Json
        String json = JSON.toJSONString(item);
        // 创建Update请求对象 参数为: 索引库名   类型名  文档ID
        UpdateRequest request = new UpdateRequest("item","_doc","1");
        // 将Json格式的数据放入到请求中
        request.doc(json,XContentType.JSON);
        // 发送请求
        UpdateResponse response = client.update(request);
        // 打印结果
        System.out.println("结果为:" + response);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    删除文档:delete

    示例:

    // 根据ID删除文档
    @Test
    void delete() throws IOException {
        // 创建Delete请求对象 参数为: 索引库名   类型名  文档ID
        DeleteRequest request = new DeleteRequest("item","_doc","1");
        // 发送请求
        DeleteResponse response = client.delete(request);
        // 打印结果
        System.out.println("结果为:" + response);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    批量新增:bulk

    示例:

        // 批量插入
        @Test
        void bulkInsert() throws IOException {
            // 准备文档数据:
            List<Item> list = new ArrayList<>();
            list.add(new Item(1L, "小米手机7", "手机", "小米", 3299.00, "http://image.leyou.com/13123.jpg"));
            list.add(new Item(2L, "坚果手机R1", "手机", "锤子", 3699.00, "http://image.leyou.com/13123.jpg"));
            list.add(new Item(3L, "华为META10", "手机", "华为", 4499.00, "http://image.leyou.com/13123.jpg"));
            list.add(new Item(4L, "小米Mix2S", "手机", "小米", 4299.00, "http://image.leyou.com/13123.jpg"));
            list.add(new Item(5L, "荣耀V10", "手机", "华为", 2799.00, "http://image.leyou.com/13123.jpg"));
            // 创建批量新增请求
            BulkRequest request = new BulkRequest();
            // 遍历集合
            for (Item item : list) {
                // 将索引请求添加到批量请求对象中
                request.add(new IndexRequest("item", "_doc", item.getId().toString())
                        .source(JSON.toJSONString(item), XContentType.JSON));
            }
            // 发送请求
            BulkResponse response = client.bulk(request);
            // 打印结果
            System.out.println("结果为:" + response);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    文档搜索:search

    查询所有:matchAllQuery

    示例:

        /**
         * 查询文档
         */
        @Test
        public void searchDoc() throws IOException {
            // 创建查询请求对象 指定查询的索引名称
            SearchRequest request = new SearchRequest("item");
            // 指定查询的源
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            // 构建查询条件
            QueryBuilder query = QueryBuilders.matchAllQuery(); // 查询的是所有的文档
    
            // 添加查询条件
            sourceBuilder.query(query);
    
            // 添加查询源
            request.source(sourceBuilder);
            // 发送请求
            SearchResponse response = client.search(request);
            // 分析响应结果
            // 返回命中的数据对象
            SearchHits searchHits = response.getHits();
            // 获取命中的文档个数
            long totalHits = searchHits.totalHits;
            System.out.println("命中的文档个数为: " + totalHits);
            // 获取命中的数据
            SearchHit[] hits = searchHits.getHits();
            // 遍历数组
            for (SearchHit hit : hits) {
                String sourceAsString = hit.getSourceAsString();
                // 转换成对象
                Item item = JSON.parseObject(sourceAsString, Item.class);
                System.out.println(item);
            }
        }
    
    • 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

    上面的代码中,搜索条件是通过 sourceBuilder.query(QueryBuilders.matchAllQuery())来添加的。这个 query() 方法接受的参数是: QueryBuilder 接口类型。

    QueryBuilder 接口提供了很多实现类,分别对应不同类型的查询,例如:term查询、match查询、range查询、boolean查询等。若要使用各种不同查询,只需传递不同的参数给 sourceBuilder.query() 方法即可。而这些实现类并不需要去 new ,官方提供了 QueryBuilders 工厂来构建各种实现类

    在这里插入图片描述


    关键字搜索:matchQuery

    其实搜索类型的变化,仅仅是利用QueryBuilders构建的查询对象不同而已,其他代码基本一致:

    // 使用match查询,参数为 1 查询的字段 2 查询的关键字
    QueryBuilder query = QueryBuilders.matchQuery("title","小米");
    
    • 1
    • 2

    关键字完全匹配:termQuery

    // 使用term查询,参数为 1 查询的字段 2 查询的关键字
    QueryBuilder query = QueryBuilders.termQuery("title","小米手机");
    
    • 1
    • 2

    范围查询:rangeQuery

    支持下面的范围关键字:

    方法说明
    gt(Object from)大于
    gte(Object from)大于等于
    lt(Object from)小于
    lte(Object from)小于等于

    示例:

    // 使用rangeQuery查询,参数为 查询的字段
    QueryBuilder query = QueryBuilders.rangeQuery("price").gte(2000).lt(4000);  // 参数为:查询的字段 后面是链式的调用
    
    • 1
    • 2

    响应:

    item = Item(id=2, title=坚果手机R1, category=手机, brand=锤子, price=3699.0, 
                images=http://image.leyou.com/13123.jpg)
    item = Item(id=5, title=荣耀V10, category=手机, brand=华为, price=2799.0, 
                images=http://image.leyou.com/13123.jpg)
    item = Item(id=1, title=小米手机7, category=手机, brand=小米, price=3299.0, 
                images=http://image.leyou.com/13123.jpg)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    过滤 :fetchSource

    默认情况下,索引库中所有数据都会返回,如果想只返回部分字段,可以通过fetchSource来控制。

    示例:

        /**
         * 查询文档
         */
        @Test
        public void searchDoc() throws IOException {
            // 创建查询请求对象 指定查询的索引名称
            SearchRequest request = new SearchRequest("item");
            // 指定查询的源
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            // 构建查询条件
            // QueryBuilder query = QueryBuilders.matchAllQuery(); // 查询的是所有的文档
    
            // 使用match查询,参数为 1 查询的字段 2 查询的关键字
            // QueryBuilder query = QueryBuilders.matchQuery("title","小米");
    
            // 使用term查询,参数为 1 查询的字段 2 查询的关键字
            // QueryBuilder query = QueryBuilders.termQuery("title","小米手机");
    
            // 使用rangeQuery查询,参数为 1 查询的字段
            QueryBuilder query = QueryBuilders.rangeQuery("price").gte(3000).lte(4000);
    
            // 添加查询条件
            sourceBuilder.query(query);
    
            // 添加过滤
            String[] includes = {"id", "title", "price"};
            String[] excludes = {};
            sourceBuilder.fetchSource(includes, excludes);
    
            // 添加查询源
            request.source(sourceBuilder);
            // 发送请求
            SearchResponse response = client.search(request);
            // 分析响应结果
            // 返回命中的数据对象
            SearchHits searchHits = response.getHits();
            // 获取命中的文档个数
            long totalHits = searchHits.totalHits;
            System.out.println("命中的文档个数为: " + totalHits);
            // 获取命中的数据
            SearchHit[] hits = searchHits.getHits();
            // 遍历数组
            for (SearchHit hit : hits) {
                String sourceAsString = hit.getSourceAsString();
                // 转换成对象
                Item item = JSON.parseObject(sourceAsString, Item.class);
                System.out.println(item);
            }
        }
    
    • 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

    排序:sort

    示例:

    // 排序
    sourceBuilder.sort("price", SortOrder.DESC);
    
    • 1
    • 2

    分页:from、size

    示例:

    // 分页
    int current = 1;
    int size = 2;
    int start = (current - 1) * size;
    sourceBuilder.from(start);
    sourceBuilder.size(size);
    // 搜索
    SearchResponse response = client.search(request);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    高亮:HighlightBuilder

    示例:

    	@Test
        public void testHighlight() throws IOException{
            // 创建搜索对象
            SearchRequest request = new SearchRequest();
            // 指定查询的源
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            // 添加查询条件,通过QueryBuilders获取各种查询
            sourceBuilder.query(QueryBuilders.matchQuery("title", "小米手机"));
    
            // 高亮
            HighlightBuilder highlightBuilder = new HighlightBuilder()	//创建高亮构建器对象
                    .field("title") // 指定高亮字段
                    .preTags("")  // 添加高亮前缀
                    .postTags(""); // 添加高亮后缀
            sourceBuilder.highlighter(highlightBuilder);
            request.source(sourceBuilder);
    
            // 获取结果
            SearchResponse response = client.search(request);
            SearchHits hits = response.getHits();
            SearchHit[] hitList = hits.getHits();
            for (SearchHit hit : hitList) {
                // 获取高亮结果
                Map<String, HighlightField> fields = hit.getHighlightFields();
                // 取出标题
                HighlightField titleField = fields.get("title");
                // 拼接为字符串
                Text[] fragments = titleField.fragments();
                String title = fragments[0].string();
                // 获取其它字段,并转换成对象
                Item item = JSON.parseObject(hit.getSourceAsString(), Item.class);
                // 覆盖title
                item.setTitle(title);
                System.out.println(item);
            }
        }
    
    • 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

    关键代码:

    • 查询条件中添加高亮字段:
      • new HighlightBuilder() :创建高亮构建器
      • .field(“title”) :指定高亮字段
      • .preTags(“”) 和 .postTags(“”) :指定高亮的前置和后置标签
    • 解析高亮结果:
      • hit.getHighlightFields(); 获取高亮结果

    聚合:aggregation

    再来试试聚合,以brand字段来聚合,看看有哪些品牌,每个品牌有多少数量。

    聚合关键是弄清楚这几点:

    • 聚合的字段是什么
    • 聚合的类型是什么
    • 给聚合起个名

    与查询类似,聚合条件通过 sourceBuilder.aggregation() 方法来设置,而参数是一个接口:

    • AggregationBuilder ,这个接口也有大量的实现类,代表不同的聚合种类。

    同样也不需要自己去new,官方提供了一个工厂帮助创建实例:

    在这里插入图片描述

    示例:

        /**
         * 聚合
         */
        @Test
        public void testAgg() throws IOException{
            // 创建搜索对象
            SearchRequest request = new SearchRequest();
            // 指定查询的源
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            // 添加查询条件,通过QueryBuilders获取各种查询
            sourceBuilder.query(QueryBuilders.matchAllQuery());
            // 添加排序
            sourceBuilder.sort("price", SortOrder.ASC);
            // 配置size为0,因为不需要数据,只要聚合结果
            sourceBuilder.size(0);
            // 添加聚合
            sourceBuilder.aggregation(AggregationBuilders.terms("brandAgg").field("brand"));
            request.source(sourceBuilder);
            // 获取结果
            SearchResponse response = client.search(request);
            // 获取聚合结果
            Aggregations aggregations = response.getAggregations();
            // 获取某个聚合
            Terms terms = aggregations.get("brandAgg");
            // 获取桶
            for (Terms.Bucket bucket : terms.getBuckets()) {
                // 获取key,这里是品牌名称
                System.out.println("品牌 : " + bucket.getKeyAsString());
                // 获取docCount,就是数量
                System.out.println("count: " + bucket.getDocCount());
            }
        }
    
    • 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

    响应:

    品牌 : 华为
    count: 2
    品牌 : 小米
    count: 2
    品牌 : 锤子
    count: 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    还可以在聚合中添加子聚合

    示例:

    	/**
         * 聚合
         */
        @Test
        public void testAgg() throws IOException{
            // 创建搜索对象
            SearchRequest request = new SearchRequest();
            // 指定查询的源
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            // 添加查询条件,通过QueryBuilders获取各种查询
            sourceBuilder.query(QueryBuilders.matchAllQuery());
            // 添加排序
            sourceBuilder.sort("price", SortOrder.ASC);
            // 配置size为0,因为不需要数据,只要聚合结果
            sourceBuilder.size(0);
    
            // 添加聚合
            TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("brandAgg").field("brand");
    
            // 添加子聚合
            termsAggregationBuilder.subAggregation(AggregationBuilders.avg("avgPrice").field("price"));
    
            sourceBuilder.aggregation(termsAggregationBuilder);
    
            request.source(sourceBuilder);
            // 获取结果
            SearchResponse response = client.search(request);
            // 获取聚合结果
            Aggregations aggregations = response.getAggregations();
            // 获取某个聚合
            Terms terms = aggregations.get("brandAgg");
            // 获取桶
            for (Terms.Bucket bucket : terms.getBuckets()) {
                // 获取key,这里是品牌名称
                System.out.println("品牌 : " + bucket.getKeyAsString());
                // 获取docCount,就是数量
                System.out.println("count: " + bucket.getDocCount());
    
                // 获取子聚合
                Avg avgPrice = bucket.getAggregations().get("avgPrice");
                System.out.println("均价:" + avgPrice.getValue());
            }
        }
    
    • 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

    响应:

    品牌 : 华为
    count: 2
    均价:3649.0
    品牌 : 小米
    count: 2
    均价:3799.0
    品牌 : 锤子
    count: 1
    均价:3699.0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Spring Data Elasticsearch

    Spring Data Elasticsearch 是Spring提供的elasticsearch组件

    Spring Data Elasticsearch 简介

    Spring Data Elasticsearch(以后简称SDE)是Spring Data项目下的一个子模块。

    查看 Spring Data的官网:https://spring.io/projects/spring-data

    在这里插入图片描述

    Spring Data 的使命是给各种数据访问提供统一的编程接口,不管是关系型数据库(如MySQL),还是
    非关系数据库(如Redis),或者类似Elasticsearch这样的索引数据库。从而简化开发人员的代码,提
    高开发效率。
    包含很多不同数据操作的模块:

    在这里插入图片描述

    在这里插入图片描述

    Spring Data Elasticsearch的页面:https://spring.io/projects/spring-data-elasticsearch

    在这里插入图片描述

    特征:

    • 支持Spring的基于 @Configuration 的Java配置方式,或者XML配置方式
    • 提供了用于操作ES的便捷工具类 ElasticsearchTemplate 。包括实现文档到POJO之间的自动智能映射。
    • 利用Spring的数据转换服务实现的功能丰富的对象映射
    • 基于注解的元数据映射方式,而且可扩展以支持更多不同的数据格式
    • 根据持久层接口自动生成对应实现方法,无需人工编写基本操作代码(类似mybatis,根据接口自动得到实现)。当然,也支持人工定制查询

    版本关系:

    在这里插入图片描述

    注意:如果使用Spring Boot 2.3.X版本,Spring Data Elasticsearch 使用的是4.0.X,在4版本后很多API与ElasticSearch 6.X兼容性不是很好,所以要将Spring Boot的版本控制在2.1.X-2.2.X。


    配置 Spring Data Elasticsearch

    官方文档:https://docs.spring.io/spring-data/elasticsearch/docs/3.0.1.RELEASE/reference/html/#reference

    集成步骤:

    1. 依赖

      <parent>
          <groupId>org.springframework.bootgroupId>
          <artifactId>spring-boot-starter-parentartifactId>
          <version>2.1.3.RELEASEversion>
          <relativePath/> 
      parent>
      <properties>
          <java.version>1.8java.version>
      properties>
      <dependencies>
          <dependency>
              <groupId>org.springframework.bootgroupId>
              <artifactId>spring-boot-starter-data-elasticsearchartifactId>
          dependency>
      
          <dependency>
              <groupId>org.projectlombokgroupId>
              <artifactId>lombokartifactId>
              <optional>trueoptional>
          dependency>
          <dependency>
              <groupId>org.springframework.bootgroupId>
              <artifactId>spring-boot-starter-testartifactId>
              <scope>testscope>
          dependency>
          <dependency>
              <groupId>com.alibabagroupId>
              <artifactId>fastjsonartifactId>
              <version>1.2.70version>
          dependency>
      dependencies>	
      
      • 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. 配置文件中添加 ES 地址

      Spring Data Elasticsearch 已经配置好了各种SDE配置,并且注册了一个 ElasticsearchTemplate 以供使用

      ElasticsearchTemplate 底层使用的不是 Elasticsearch 提供的 RestHighLevelClient,而是 TransportClient,并不采用 Http 协议通信,而是访问 elasticsearch 对外开放的 tcp 端口,所以这里设置的端口是:9300 ,而不是9200

      spring:
        data:
          elasticsearch:
            # ES 集群名称
            cluster-name: elasticsearch
            # 这里使用的是TransportClient 连接的是TCP端口
            cluster-nodes: localhost:9300,localhost:9301,localhost:9302
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

    索引库操作(ElasticsearchTemplate)

    创建索引库

    添加测试类,这里需要注意 SpringBoot 2.1.X 的测试类上需要添加 @RunWith(SpringRunner.class),注入ElasticsearchTemplate

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class EsDemoApplicationTests {
        @Autowired
    	private ElasticsearchTemplate template;
    }    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    准备一个实体类

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Document(indexName = "goods",type = "_doc",shards = 3, replicas = 1)
    public class Goods {
        @Id
        private Long id;
        @Field(type = FieldType.Text, analyzer = "ik_max_word")
        private String title; //标题
        @Field(type = FieldType.Keyword)
        private String category;// 分类
        @Field(type = FieldType.Keyword)
        private String brand; // 品牌
        @Field(type = FieldType.Double)
        private Double price; // 价格
        @Field(type = FieldType.Keyword, index = false)
        private String images; // 图片地址
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    用到的注解的说明:

    • @Document:声明索引库配置
      • indexName:索引库名称
      • shards:分片数量,默认5
      • replicas:副本数量,默认1
    • @Id:声明实体类的id
    • @Field:声明字段属性
      • type:字段的数据类型
      • analyzer:指定分词器类型
      • index:是否创建索引

    创建索引库

        /**
         * 创建索引
         */
        @Test
        void testCreateIndex(){
            boolean b = template.createIndex(Goods.class);
            System.out.println("结果为:"+b);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    若不加@Document注解,直接运行会报错,如下所示:

    在这里插入图片描述

    加@Document注解再次运行测试,可以成功创建索引,看一下索引信息

    在这里插入图片描述


    创建映射

    @Id 和 @Filed 注解用于配置映射关系,使用 putMapping() 方法即可创建映射:

    /**
     * 创建类型映射
     */
    @Test
    public void testCreateMapping() {
        boolean b = template.putMapping(Goods.class);
        System.out.println("结果为:" + b);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    查看索引信息

    在这里插入图片描述


    新增文档

        @Autowired
    	private ElasticsearchTemplate template;
    
        @Test
        public void add(){
            Goods goods = new Goods(1L,"小米手机10Pro","手机","小米",5999.00,"/images/123.jpg");
            IndexQuery query = new IndexQuery();
            query.setObject(goods);
            String index = template.index(query);
            System.out.println(index);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    ElasticsearchRepository 接口

    ElasticsearchRepository封装了基本的CRUD方法,可以通过继承 ElasticsearchRepository 来使用:

    public interface GoodsRepository extends ElasticsearchRepository<Goods,Long> {
    }
    
    • 1
    • 2

    ElasticsearchRepository 中的T对应类型,ID对应主键类型。


    新增和更新文档:save

    新增单个文档

    // 保存文档
    @Test
    public void testSave() {
        Goods item = new Goods(6L, "小米手机10Pro", " 手机", "小米", 4699.00, "http://image.leyou.com/13123.jpg");
        goodsRepository.save(item);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    更新文档

    // 更新文档
    @Test
    public void testUpdate() {
        Goods item = new Goods(6L, "小米手机10Pro", " 手机", "小米", 4699.00, "http://image.leyou.com/13123.jpg");
        goodsRepository.save(item);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    批量新增

    @Test
    // 批量保存文档
    public void addDocuments() {
        // 准备文档数据:
        List<Goods> list = new ArrayList<>();
        list.add(new Goods(1L, "小米手机7", "手机", "小米", 3299.00, "/13123.jpg"));
        list.add(new Goods(2L, "坚果手机R1", "手机", "锤子", 3699.00, "/13123.jpg"));
        list.add(new Goods(3L, "华为META10", "手机", "华为", 4499.00, "/13123.jpg"));
        list.add(new Goods(4L, "小米Mix2S", "手机", "小米", 4299.00, "/13123.jpg"));
        list.add(new Goods(5L, "荣耀V10", "手机", "华为", 2799.00, "/13123.jpg"));
        // 添加索引数据
        goodsRepository.saveAll(list);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    查看文档:findById

    根据id查询

    // 根据id查询
    @Test
    public void testQueryById(){
        Optional<Goods> goodsOptional = goodsRepository.findById(3L);
        System.out.println(goodsOptional.orElse(null));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    删除文档:deleteById

    根据id删除

    // 删除文档
    @Test
    public void testDelete(){
        goodsRepository.deleteById(6L);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    查询所有:findAll

    // 查询所有
    @Test
    public void testQueryAll(){
        Iterable<Goods> list = goodsRepository.findAll();
        list.forEach(System.out::println);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    自定义查询

    GoodsRepository提供的查询方法有限,但是它却提供了非常强大的自定义查询功能:只要遵循SpringData提供的语法,就可以任意定义方法声明:

    public interface GoodsRepository extends ElasticsearchRepository<Goods, Long> {
        /**
         * 根据价格区间查询
         * @param from 开始价格
         * @param to 结束价格
         * @return 符合条件的goods
         */
        List<Goods> findByPriceBetween(double from, double to);
        List<Goods> findByTitle(String title);
        List<Goods> findByBrand(String brand);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    使用实例:

    // 范围查询
    @Test
    public void testConditionSearch(){
        List<Goods> list = goodsRepository.findByPriceBetween(3000, 4000);
        list.forEach(System.out::println);
    }
    @Test
    public void testTitle(){
        // List goods = goodsRepository.findByBrand("米");
        List<Goods> goods = goodsRepository.findByBrand("小米");
        goods.forEach(System.out::println);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    支持的一些语法示例:

    KeywordSampleElasticsearch Query String
    AndfindByNameAndPrice{"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}
    OrfindByNameOrPrice{"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}
    IsfindByName{"bool" : {"must" : {"field" : {"name" : "?"}}}}
    NotfindByNameNot{"bool" : {"must_not" : {"field" : {"name" : "?"}}}}
    BetweenfindByPriceBetween{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
    LessThanEqualfindByPriceLessThan{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
    GreaterThanEqualfindByPriceGreaterThan{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}
    BeforefindByPriceBefore{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
    AfterfindByPriceAfter{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}
    LikefindByNameLike{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}}
    StartingWithfindByNameStartingWith{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}}
    EndingWithfindByNameEndingWith{"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}}
    Contains/ContainingfindByNameContaining{"bool" : {"must" : {"field" : {"name" : {"query" : "**?**","analyze_wildcard" : true}}}}}
    InfindByNameIn(Collectionnames){"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}}
    NotInfindByNameNotIn(Collectionnames){"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}}
    NearfindByStoreNearNot Supported Yet !
    TruefindByAvailableTrue{"bool" : {"must" : {"field" : {"available" : true}}}}
    FalsefindByAvailableFalse{"bool" : {"must" : {"field" : {"available" : false}}}}
    OrderByfindByAvailableTrueOrderByNameDesc{"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}}

    条件查询:matchQuery

    @Test
    public void testSearch(){
        // QueryBuilder query = QueryBuilders.matchAllQuery();
        QueryBuilder query = QueryBuilders.matchQuery("title","小米");
        Iterable<Goods> goods = goodsRepository.search(query);
        goods.forEach(System.out::println);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    分页查询:search

    @Test
    public void testPage(){
        QueryBuilder query = QueryBuilders.matchAllQuery();
        // 设置分页 page是从0开始
        PageRequest pageable = PageRequest.of(0, 2, Sort.by(Sort.Direction.DESC, "price"));
        Page<Goods> goodsPage = goodsRepository.search(query, pageable);
        System.out.println("总数:" + goodsPage.getTotalElements());
        List<Goods> goods = goodsPage.getContent();
        goods.forEach(System.out::println);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    ElasticsearchTemplate 接口

    SDE也支持使用 ElasticsearchTemplate 进行原生查询,而查询条件的构建是通过一个名为 NativeSearchQueryBuilder 的类来完成的,不过这个类的底层还是使用的原生API中的 QueryBuilders 、 AggregationBuilders 、 HighlightBuilders 等工具。

    高亮

    要支持高亮,必须自定义结果处理器来实现

    import com.itheima.es.entity.Goods;
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.search.SearchHit;
    import org.elasticsearch.search.SearchHits;
    import org.elasticsearch.search.aggregations.Aggregations;
    import org.springframework.data.domain.Pageable;
    import org.springframework.data.elasticsearch.core.SearchResultMapper;
    import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
    import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
    import java.util.ArrayList;
    import java.util.List;
    
    public class GoodsSearchResultMapper implements SearchResultMapper {
        @Override
        public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
            SearchHits searchHits = response.getHits();
            long total = searchHits.getTotalHits();
            float maxScore = searchHits.getMaxScore();
            // 定义content
            List<T> content = new ArrayList<>();
            SearchHit[] hits = searchHits.getHits();
            // 遍历文档
            for (SearchHit hit : hits) {
                // 获取json格式数据
                String sourceAsString = hit.getSourceAsString();
                // 转换称为对象
                Goods goods = JSON.parseObject(sourceAsString, Goods.class);
                // 解析高亮字段
                String title = hit.getHighlightFields().get("title").fragments()[0].string();
                // 替换原有的title
                goods.setTitle(title);
                content.add((T) goods);
            }
            Aggregations aggregations = response.getAggregations();
            String scrollId = response.getScrollId();
    
            return new AggregatedPageImpl(content,pageable,total,aggregations,scrollId,maxScore);
        }
    }
    
    • 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

    查询时需要传入自定义结果处理器

    	/**
         * 查询结果高亮处理
         */
        @Test
        public void testHighlight() {
            // 构建查询条件
            QueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "小米");
            // 定义高亮条件
            HighlightBuilder.Field field = new HighlightBuilder.Field("title")
                    .preTags("")
                    .postTags("");
            // 构建查询条件并设置高亮
            SearchQuery query = new NativeSearchQueryBuilder()
                    .withQuery(queryBuilder)
                    .withHighlightFields(field)
                    .build();
            AggregatedPage<Goods> aggregatedPage = template.queryForPage(query, Goods.class, new GoodsSearchResultMapper());
            List<Goods> goods = aggregatedPage.getContent();
            goods.forEach(System.out::println);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    查看结果:

    在这里插入图片描述


    聚合

    示例:

        /**
         * 聚合
         */
        @Test
        void testAgg() {
            // 针对品牌字段做分组
            AbstractAggregationBuilder agg = AggregationBuilders.terms("brandAgg").field("brand");
            // 添加子聚合来实现平均值的计算
            agg.subAggregation(AggregationBuilders.avg("priceAvg").field("price"));
            SearchQuery query = new NativeSearchQueryBuilder()
                    .withQuery(QueryBuilders.matchAllQuery())
                    .addAggregation(agg)
                    .build();
            AggregatedPage<Goods> aggregatedPage = template.queryForPage(query, Goods.class);
            // 获取到品牌的聚合
            Terms brandAgg = (Terms) aggregatedPage.getAggregation("brandAgg");
            for (Terms.Bucket bucket : brandAgg.getBuckets()) {
                System.out.println("品牌:" + bucket.getKeyAsString());
                System.out.println("数量:" + bucket.getDocCount());
                Avg priceAvg = bucket.getAggregations().get("priceAvg");
                System.out.println("均价:" + priceAvg.getValue());
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    结果:

    在这里插入图片描述

  • 相关阅读:
    什么品牌洗地机性价比高?四大出色的王牌机型力荐
    来看下这篇文章,教你如何实现一个SpringBoot的Mybatis分库分表组件
    树形模拟+随机函数+二分
    Python Opencv实践 - 视频目标追踪CamShift
    初体验:动手搭建“小清新式“、版本控制、持续集成的局域网文档系统
    第六章 图 十、关键路径
    一文细谈SNN的基本数学原理,LIF模型,STDP与STBP学习方法
    Laya:【音效BUG】在游戏失焦后再次返回游戏导致音效播放异常的问题与解决方案
    Java 最常见的面试题:常用的 jvm 调优的参数都有哪些?
    【数据结构】模拟实现LinkedList
  • 原文地址:https://blog.csdn.net/footless_bird/article/details/127635368