Spring全家桶之SpringData——SpringDataElasticSearch

简介

使用 Spring Data 下二级子项目 Spring Data Elasticsearch 进行操作。
支持 POJO 方 法操作 Elasticsearch。相比 Elasticsearch 提供的 API 更加简单更加方便。

前提

实现

搭建SpringDataElasticSearch框架

  1. 创建Maven项目, 添加Springdata-es相关坐标

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>springboot-es</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>2.2.5.RELEASE</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <dependencies>
            <!--springboot对es的依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
            </dependency>
            <!--单元测试使用-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
            </dependency>
    
        </dependencies>
    </project>
    
  2. 创建启动类

    /**
     * Springboot整合ES的启动器
     */
    @SpringBootApplication
    public class SpringbootEsApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringbootEsApplication.class, args);
        }
    }
    
  3. 修改配置文件

    集群版多地址之间使用逗号分隔。
    在 ElasticSearch5.x 以前的版本中,客户端使用的是 Transport 客户端,通过 TCP 协议和 9300 端口访问ES。在 6.x 及之后的版本中,官方推荐使用 Rest 客户端,通过 Http 协议和 9200 端口访问 ES。
    在新版的 Spring Data Elasticsearch 框架中,Transport 客 户端配置已经设置为过时配置,推荐使用 Rest 客户端.

    spring:
    #  data:
    #    elasticsearch:
    #      cluster-name: elasticsearch # 必须提供的配置,集群的名称。
    #      cluster-nodes:  192.168.40.20:9300,192.168.40.21:9300  # transport客户端的端口是9300
      elasticsearch:
        rest:  # 配置ElasticsearchRestTemplate客户端的属性,是现在推荐使用的。
          uris:
            - http://192.168.40.20:9200
            - http://192.168.40.21:9200
    
  4. 创建用于进行映射的 pojo类

    import org.springframework.data.annotation.Id;
    import org.springframework.data.elasticsearch.annotations.Document;
    import org.springframework.data.elasticsearch.annotations.Field;
    import org.springframework.data.elasticsearch.annotations.FieldType;
    
    /**
     * 自定义类型,商品。
     * 让自定义的类型和ElasticSearch中的一个索引产生关联。
     * Document - spring data elasticsearch提供的注解, 描述类型,说明类型和索引的关系。
     *  indexName - 对应的索引的名称。 必要属性。
     *  shards - 创建索引时,设置的主分片数量。 默认5
     *  replicas - 创建索引时,设置的副本分片数量。 默认1
     *  type - 对应的类型的名称。
     * @create 2021-02-24 上午 10:42
     */
    @Document(indexName = "hrt-item", shards = 2, replicas = 2, type = "item")
    public class Item {
         /**
          * Id注解是Spring Data核心工程提供的,是所有的Spring Data二级子工程通用的。
          * 代表主键字段。
          */
         @Id
         private String id;
         /**
          * Field注解,描述实体类型中属性和ES索引中字段的关系。
          * 且可以为这个字段配置自定义映射mapping
          * 这个自定义映射必须通过代码逻辑调用设置映射的API才能生效。
          *    name - 索引中对应的字段名称,默认和属性同名。
          *    type - 索引中字段的类型,默认是FieldType.Auto,代表ES自动映射类型。
          *    analyzer - 字段的分词器名称,默认是standard。
          *    fielddata - 是否开启正向索引。默认关闭。
          *                默认只为文本类型的字段创建反向索引,提供快速搜索逻辑。
          *                fielddata设置为true,则会额外创建一个正向索引,支持排序。
          *    index - 是否创建默认的反向索引或正向索引。 text文本类型字段默认创建反向索引,其他创建正向索引。
          *            没有索引,就不能作为搜索条件。
          */
         @Field(name = "title", type = FieldType.Text, analyzer = "ik_max_word", fielddata = true)
         private String title; // 商品名称,需要中文分词,且偶尔需要排序, 常用搜索条件之一
         @Field(name = "sellPoint", type = FieldType.Text, analyzer = "ik_max_word")
         private String sellPoint; // 卖点, 需要中文分词, 常用搜索条件之一
         @Field(type = FieldType.Long)
         private Long price; // 单价
         @Field(type = FieldType.Integer, index = false)
         private int num; // 库存
    
        public Item() {
        }
    
        public Item(String id, String title, String sellPoint, Long price, int num) {
            this.id = id;
            this.title = title;
            this.sellPoint = sellPoint;
            this.price = price;
            this.num = num;
        }
    
    
    
        public void setId(String id) {
            this.id = id;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public void setSellPoint(String sellPoint) {
            this.sellPoint = sellPoint;
        }
    
        public void setPrice(Long price) {
            this.price = price;
        }
    
        public void setNum(int num) {
            this.num = num;
        }
    
        public String getId() {
            return id;
        }
    
        public String getTitle() {
            return title;
        }
    
        public String getSellPoint() {
            return sellPoint;
        }
    
        public Long getPrice() {
            return price;
        }
    
        public int getNum() {
            return num;
        }
    
        @Override
        public String toString() {
            return "Item{" +
                    "id='" + id + '\'' +
                    ", title='" + title + '\'' +
                    ", sellPoint='" + sellPoint + '\'' +
                    ", price=" + price +
                    ", num=" + num +
                    '}';
        }
    }
    
  5. 创建测试类

    /**
     * 测试boot整合es
     */
    @SpringBootTest(classes = {SpringbootEsApplication.class})
    @RunWith(SpringRunner.class)
    public class testES {
    
        @Autowired
        private ElasticsearchRestTemplate restTemplate;
    
        /**
         * 创建索引,并设置映射。
         * 需要通过两次访问实现,1、创建索引;2、设置映射。
         */
        @Test
        public void testInitIndex(){
            // 创建索引,根据类型上的Document注解创建
            boolean isCreated = restTemplate.createIndex(Item.class);
            // 设置映射,根据属性上的Field注解设置 0201
            boolean isMapped = restTemplate.putMapping(Item.class);
            System.out.println("创建索引是否成功:" + isCreated);
            System.out.println("设置映射是否成功:" + isMapped);
        }
    }
    
  6. 查询es中是否生成相关index

    GET _cat/indices
    DELETE hrt-item
    

    在这里插入图片描述

相关操作

创建索引

createIndex(): 创建索引,创建出来的索引是不带有 mapping 信息的。返回值表示是 否创建成功
putMapping():为已有的索引添加 mapping 信息。不具备创建索引的能力。返回值表 示是否创建成功

 /**
	     * 创建索引,并设置映射。
	     * 需要通过两次访问实现,1、创建索引;2、设置映射。
	     */
	    @Test
	    public void testInitIndex(){
	        // 创建索引,根据类型上的Document注解创建
	        boolean isCreated = restTemplate.createIndex(Item.class);
	        // 设置映射,根据属性上的Field注解设置 0201
	        boolean isMapped = restTemplate.putMapping(Item.class);
	        System.out.println("创建索引是否成功:" + isCreated);
	        System.out.println("设置映射是否成功:" + isMapped);
	    }

在kibana的dev tools中查看

//查询索引
GET _cat/indices
//查询mapping映射
GET hrt-item/_mapping

删除索引

/**
     * 删除索引
     */
    @Test
    public void deleteIndex(){
        // 扫描Item类型上的Document注解,删除对应的索引。
        boolean isDeleted = restTemplate.deleteIndex(Item.class);
        System.out.println("删除Item对应索引是否成功:" + isDeleted);
        // 直接删除对应名称的索引。
        isDeleted = restTemplate.deleteIndex("test_index3");
        System.out.println("删除default_index索引是否成功:" + isDeleted);
    }

在kibana的dev tools中查看

//查询索引
GET _cat/indices

新增文档

如果索引和类型不存在,也可以执行进行新增,新增后自动创建索引和类型。但是 field 通过动态 mapping 进行映射,elaticsearch 根据值类型进行判断每个属性类型,默认每个属性都是 standard 分词器,ik 分词器是不生效的。所以一定要先通过代码进行初始化或直接在 elasticsearch 中通过命令创建所有 field 的 mapping

如果对象的 id 属性没有赋值,让 ES 自动生成主键,存储时 id 属性没有值,_id 存储 document 的主键值。
如果对象的 id 属性明确设置值,存储时 id 属性为设置的值,ES 中 document 对象的 _id 也是设置的值

    /**
     * 新增数据到ES
     */
    @Test
    public void testInsert(){
        Item item = new Item();
        item.setId("19216811");
        item.setTitle("沃什.伊戈.史莱姆");
        item.setSellPoint("关于我成为史莱姆却因为铺垫太长遭人骂这档事");
        item.setPrice(996666L);
        item.setNum(233);

        //这个是构建器放入的方式
        IndexQuery indexQuery = new IndexQueryBuilder() // 创建一个IndexQuery的构建器
                                    .withObject(item) // 设置要新增的Java对象
                                    .build(); // 构建IndexQuery类型的对象。
        //这个是直接放入的方式
//        IndexQuery query = new IndexQuery();
//        query.setObject(item);
        // index逻辑,相当于使用PUT请求,实现数据的新增。
        String result = restTemplate.index(indexQuery);
        System.out.println(result);
    }

在kibana的dev tools中查看

//查询改索引下所有文档
GET hrt-item/_search

批量新增文档

/**
     * 批量新增
     * bulk操作
     */
    @Test
    public void testBatchInsert(){
        List<IndexQuery> queries = new ArrayList<IndexQuery>();
        Item item = new Item();
        item.setId("20210224");
        item.setTitle("IPHONE 12 手机");
        item.setSellPoint("很贵");
        item.setPrice(499900L);
        item.setNum(999);
        queries.add(new IndexQueryBuilder().withObject(item).build());
        item = new Item();
        item.setId("20210225");
        item.setTitle("华为P40照相手机");
        item.setSellPoint("可以拍月亮");
        item.setPrice(599900L);
        item.setNum(999);
        queries.add(new IndexQueryBuilder().withObject(item).build());
        item = new Item();
        item.setId("20210226");
        item.setTitle("红米k30");
        item.setSellPoint("大品牌值得信赖");
        item.setPrice(699900L);
        item.setNum(999);
        queries.add(new IndexQueryBuilder().withObject(item).build());
        // 批量新增,使用的是bulk操作。
        restTemplate.bulkIndex(queries);
    }

在kibana的dev tools中查看

//查询改索引下所有文档
GET hrt-item/_search

删除文档

根据主键删除 delete(String indexName,String typeName,String id); 通过字符串指定索引,类型 和 id 值delete(Class,String id) 第一个参数传递实体类类类型,建议使用此方法,减少索引名 和类型名由于手动编写出现错误的概率。 返回值为 delete 方法第二个参数值(删除文档的主键值)

    /**
     * 删除文档
     */
    @Test
    public void testDelete(){
        // 根据主键删除, 常用
        String result = restTemplate.delete(Item.class, "192168111");
        System.out.println(result);

        // 根据查询结果,删除查到的数据。 应用较少。
        /*DeleteQuery query = new DeleteQuery();
        query.setIndex("hrt-item");
        query.setType("item");
        query.setQuery(QueryBuilders.matchQuery("title", "沃什.伊戈.史莱姆1"));
        restTemplate.delete(query, Item.class);*/
    }

在kibana的dev tools中查看

//查询改索引下所有文档
GET hrt-item/_search

修改文档

修改操作就是新增代码,只要保证主键 id 已经存在,新增就是修改。
如果使用部分更新,则需要通过 update 方法实现。具体如下:

/**
     * 修改文档
     * 如果是全量替换,可以使用index方法实现,只要主键在索引中存在,就是全量替换。<=>新增or批量新增操作
     * 如果是部分修改,则可以使用update实现。
     */
    @Test
    public void testUpdate() throws Exception{
        UpdateRequest request = new UpdateRequest();
        request.doc(
                XContentFactory.jsonBuilder()
                        .startObject()
                        .field("name", "测试update更新数据,商品名称")
                        .endObject()
        );
        UpdateQuery updateQuery =
                new UpdateQueryBuilder()
                        .withUpdateRequest(request)
                        .withClass(Item.class)
                        .withId("20210224")
                        .build();
        restTemplate.update(updateQuery);
    }

在kibana的dev tools中查看

//查询改索引下所有文档
GET hrt-item/_search

搜索所有数据

 /**
     * 搜索所有数据
     */
    @Test
    public void testMatchAll(){
        /*
         * SearchQuery - 是Spring Data Elasticsearch中定义的一个搜索接口
         * NativeSearchQuery - 是SearchQuery接口的实现类。
         *  构造的时候,需要提供一个QueryBuilder类型的对象,
         *  QueryBuilder是Elasticsearch的java客户端中定义的搜索条件类型。
         *
         * QueryBuilders - 是QueryBuilder类型的工具类,可以快速实现QueryBuilder类型对象的创建
         *  工具类中,提供了大量的静态方法,方法命名和DSL搜索中的条件关键字相关。
         *  如:match_all 对应 matchAllQuery()
         *  如:match 对应 matchQuery()
         *  如:range 对应 rangeQuery()
         */
        SearchQuery query = new NativeSearchQuery(
                QueryBuilders.matchAllQuery()
        );
        List<Item> itemList = restTemplate.queryForList(query, Item.class);
        for(Item item : itemList){
            System.out.println(item);
        }
    }

条件搜索 or 模糊搜索

去所有 field 中搜索指定条件。
在kibana的dev tools中修改之前批量新增的一个数据, 目的是当我们搜索华为Mate40 时, 不仅能够搜索到

PUT hrt-item/item/20210226
{
  "id" : "20210226",
          "title" : "华为荣耀手机",
          "sellPoint" : "打游戏值得拥有",
          "price" : 399900,
          "num" : 999
}
    /**
     * 条件搜索
     */
    @Test
    public void testMatch(){
        SearchQuery query = new NativeSearchQuery(QueryBuilders.matchQuery("title", "华为mate40"));
        List<Item> itemList = restTemplate.queryForList(query, Item.class);
        for(Item item : itemList){
            System.out.println(item);
        }
    }

短语搜索

短语搜索是对条件不分词,但是文档中属性根据配置实体类时指定的分词类型进行分词。
如果属性使用 ik 分词器,从分词后的索引数据中进行匹配。

	/**
     * 短语搜索
     * 只要被分词后锁短语含有, 则都会被搜索到
     */
    @Test
    public void testMatchPhrase(){
        SearchQuery query = new NativeSearchQuery(
                QueryBuilders.matchPhraseQuery("title", "华为mate40")
        );
        List<Item> itemList = restTemplate.queryForList(query, Item.class);
        for(Item item : itemList){
            System.out.println(item);
        }
    }

词组搜索

	/**
     * 词组搜索
     * 只有没有被切分的词组才能搜到结果, 例如: 华为荣耀已被切分, 则搜索不到; 而荣耀 不会被切分因此能够搜索到结果
     * 区别于短语搜索
     */
    @Test
    public void testTerm(){
        SearchQuery query = new NativeSearchQuery(
                QueryBuilders.termQuery("title", "mate40")
        );
        List<Item> itemList = restTemplate.queryForList(query, Item.class);
        for(Item item : itemList){
            System.out.println(item);
        }
    }

范围搜索

/**
     * 范围搜索 range
     * gte <==> 小于等于 ; gt <==> 小于 
     * lte <==> 大于等于 ; lt <==> 大于
     */
    @Test
    public void testRange(){
        SearchQuery query = new NativeSearchQuery(
                QueryBuilders.rangeQuery("price").gte(500000L).lte(400000L)
        );
        List<Item> itemList = restTemplate.queryForList(query, Item.class);
        for(Item item : itemList){
            System.out.println(item);
        }
    }

复合条件搜索

/**
     * 复合条件搜索
     */
    @Test
    public void testBool(){
        // 创建一个Bool搜索条件。 相当于定义 bool:{ must:[], should:[], must_not:[] }
        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        List<QueryBuilder> mustList = builder.must();
        //词组所搜+范围搜索
        mustList.add(QueryBuilders.matchQuery("title", "华为"));
        mustList.add(QueryBuilders.rangeQuery("price").gte(300000L));
        // builder.mustNot();
        // builder.should();
        SearchQuery query = new NativeSearchQuery(builder);
        List<Item> itemList = restTemplate.queryForList(query, Item.class);
        for(Item item : itemList){
            System.out.println(item);
        }
    }

分页和排序搜索

如果实体类中主键只有@Id 注解,String id 对应 ES 中是 text 类型,text 类型是不允许被排序,所以如果必须按照主键进行排序时需要在实体类中设置主键类型
@Id @Field(type = FieldType.Keyword) private String id;

/**
     * 分页和排序
     * 所有的Spring Data子工程中的分页和排序逻辑使用的都是相似的方式。
     * 根据PageRequest和Sort实现分页或排序。
     */
    @Test
    public void testPageable(){
        SearchQuery query = new NativeSearchQuery(
                QueryBuilders.matchAllQuery()
        );
        // 设置分页
        query.setPageable(PageRequest.of(0, 2));
        // 设置排序
        query.addSort(Sort.by(Sort.Direction.DESC, "price"));
        // 设置分页的同时设置排序
        // query.setPageable(PageRequest.of(0, 2, Sort.by(Sort.Direction.DESC, "price")))
        List<Item> itemList = restTemplate.queryForList(query, Item.class);
        for(Item item : itemList){
            System.out.println(item);
        }
    }

高亮搜索

以上操作详见 ES 教程的 第六部分的3.1 - 3.6 , 有对应DSL操作的演示

	/**
     * 高亮
     */
    @Test
    public void testHighlight(){
        HighlightBuilder.Field field = new HighlightBuilder.Field("title");
        field.preTags("<em>");
        field.postTags("</em>");

        NativeSearchQuery query =
                new NativeSearchQueryBuilder()
                        // 排序
                        .withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC))
                        // 分页
                        .withPageable(PageRequest.of(0, 2))
                        // 搜索条件
                        .withQuery(QueryBuilders.matchQuery("title", "华为"))
                        // 设置高亮字段
                        .withHighlightFields(field)
                        .build();

        AggregatedPage<? extends Item> pageResult =
                restTemplate.queryForPage(query, Item.class, new SearchResultMapper() {
                    // 处理搜索结果,搜索的完整结果,也就是那个集合。
                    // response - 就是搜索的结果,相当于在Kibana中执行搜索的结果内容。
                    // clazz - 就是返回结果的具体类型
                    // pageable - 分页处理,就是queryForPage方法参数query中的pageable对象。
                    public <T> AggregatedPage<T> mapResults(SearchResponse response,
                                                            Class<T> clazz,
                                                            Pageable pageable) {
                        // 获取搜索的结果数据
                        SearchHit[] hits = response.getHits().getHits();
                        List<T> resultList = new ArrayList<T>();
                        for(SearchHit hit : hits){
                            // 搜索的source源
                            Map<String, Object> map = hit.getSourceAsMap();
                            Item item = new Item();
                            item.setId(map.get("id").toString());
                            item.setSellPoint(map.get("sellPoint").toString());
                            item.setPrice(Long.parseLong(map.get("price").toString()));
                            item.setNum(Integer.parseInt(map.get("num").toString()));
                            // 高亮数据处理。key - 字段名, value - 是高亮数据结果
                            Map<String, HighlightField> highlightFieldMap = hit.getHighlightFields();
                            HighlightField highlightField = highlightFieldMap.get("title");
                            if (highlightField == null){ // 没有高亮的title
                                item.setTitle(map.get("title").toString());
                            }else{ // 有高亮的title
                                item.setTitle(highlightField.getFragments()[0].toString());
                            }
                            resultList.add((T)item);
                        }
                       // 返回处理后的结果

                        return new AggregatedPageImpl<T>(
                                resultList, pageable, response.getHits().getTotalHits()
                        );
                    }

                    // 不提供实现,这个是处理每个搜索结果的方法
                    public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
                        return null;
                    }
                });

        for(Item item : pageResult.getContent()){
            System.out.println(item);
        }
    }

测试类整体代码如下:

package com.hrt.test;


import com.hrt.SpringbootEsApplication;
import com.hrt.pojo.Item;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
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 org.springframework.data.elasticsearch.core.query.*;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 测试boot整合es
 *
 * @author caohaiyang
 * @create 2021-02-24 上午 11:02
 */
@SpringBootTest(classes = {SpringbootEsApplication.class})
@RunWith(SpringRunner.class)
public class testES {

    @Autowired
    private ElasticsearchRestTemplate restTemplate;


    /**
     * 高亮
     */
    @Test
    public void testHighlight(){
        HighlightBuilder.Field field = new HighlightBuilder.Field("title");
        field.preTags("<em>");
        field.postTags("</em>");

        NativeSearchQuery query =
                new NativeSearchQueryBuilder()
                        // 排序
                        .withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC))
                        // 分页
                        .withPageable(PageRequest.of(0, 2))
                        // 搜索条件
                        .withQuery(QueryBuilders.matchQuery("title", "华为"))
                        // 设置高亮字段
                        .withHighlightFields(field)
                        .build();

        AggregatedPage<? extends Item> pageResult =
                restTemplate.queryForPage(query, Item.class, new SearchResultMapper() {
                    // 处理搜索结果,搜索的完整结果,也就是那个集合。
                    // response - 就是搜索的结果,相当于在Kibana中执行搜索的结果内容。
                    // clazz - 就是返回结果的具体类型
                    // pageable - 分页处理,就是queryForPage方法参数query中的pageable对象。
                    public <T> AggregatedPage<T> mapResults(SearchResponse response,
                                                            Class<T> clazz,
                                                            Pageable pageable) {
                        // 获取搜索的结果数据
                        SearchHit[] hits = response.getHits().getHits();
                        List<T> resultList = new ArrayList<T>();
                        for(SearchHit hit : hits){
                            // 搜索的source源
                            Map<String, Object> map = hit.getSourceAsMap();
                            Item item = new Item();
                            item.setId(map.get("id").toString());
                            item.setSellPoint(map.get("sellPoint").toString());
                            item.setPrice(Long.parseLong(map.get("price").toString()));
                            item.setNum(Integer.parseInt(map.get("num").toString()));
                            // 高亮数据处理。key - 字段名, value - 是高亮数据结果
                            Map<String, HighlightField> highlightFieldMap = hit.getHighlightFields();
                            HighlightField highlightField = highlightFieldMap.get("title");
                            if (highlightField == null){ // 没有高亮的title
                                item.setTitle(map.get("title").toString());
                            }else{ // 有高亮的title
                                item.setTitle(highlightField.getFragments()[0].toString());
                            }
                            resultList.add((T)item);
                        }
                       // 返回处理后的结果

                        return new AggregatedPageImpl<T>(
                                resultList, pageable, response.getHits().getTotalHits()
                        );
                    }

                    // 不提供实现,这个是处理每个搜索结果的方法
                    public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
                        return null;
                    }
                });

        for(Item item : pageResult.getContent()){
            System.out.println(item);
        }
    }

    /**
     * 分页和排序
     * 所有的Spring Data子工程中的分页和排序逻辑使用的都是相似的方式。
     * 根据PageRequest和Sort实现分页或排序。
     */
    @Test
    public void testPageable(){
        SearchQuery query = new NativeSearchQuery(
                QueryBuilders.matchAllQuery()
        );
        // 设置分页
        query.setPageable(PageRequest.of(0, 2));
        // 设置排序
        query.addSort(Sort.by(Sort.Direction.DESC, "price"));
        // 设置分页的同时设置排序
        // query.setPageable(PageRequest.of(0, 2, Sort.by(Sort.Direction.DESC, "price")))
        List<Item> itemList = restTemplate.queryForList(query, Item.class);
        for(Item item : itemList){
            System.out.println(item);
        }
    }

    /**
     * 复合条件搜索
     */
    @Test
    public void testBool(){
        // 创建一个Bool搜索条件。 相当于定义 bool:{ must:[], should:[], must_not:[] }
        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        List<QueryBuilder> mustList = builder.must();
        mustList.add(QueryBuilders.matchQuery("title", "华为"));
        mustList.add(QueryBuilders.rangeQuery("price").gte(300000L));
        // builder.mustNot();
        // builder.should();
        SearchQuery query = new NativeSearchQuery(builder);
        List<Item> itemList = restTemplate.queryForList(query, Item.class);
        for(Item item : itemList){
            System.out.println(item);
        }
    }

    /**
     * 范围搜索 range
     * gte <==> 小于等于 ; gt <==> 小于
     * lte <==> 大于等于 ; lt <==> 大于
     */
    @Test
    public void testRange(){
        SearchQuery query = new NativeSearchQuery(
                QueryBuilders.rangeQuery("price").gte(500000L).lte(400000L)
        );
        List<Item> itemList = restTemplate.queryForList(query, Item.class);
        for(Item item : itemList){
            System.out.println(item);
        }
    }

    /**
     * 词组搜索
     * 只有没有被切分的词组才能搜到结果, 例如: 华为荣耀已被切分, 则搜索不到; 而荣耀 不会被切分因此能够搜索到结果
     * 区别于短语搜索
     */
    @Test
    public void testTerm(){
        SearchQuery query = new NativeSearchQuery(
                QueryBuilders.termQuery("title", "荣耀")
        );
        List<Item> itemList = restTemplate.queryForList(query, Item.class);
        for(Item item : itemList){
            System.out.println(item);
        }
    }

    /**
     * 短语搜索
     */
    @Test
    public void testMatchPhrase(){
        SearchQuery query = new NativeSearchQuery(
                QueryBuilders.matchPhraseQuery("title", "华为荣耀")
        );
        List<Item> itemList = restTemplate.queryForList(query, Item.class);
        for(Item item : itemList){
            System.out.println(item);
        }
    }

    /**
     * 条件搜索
     */
    @Test
    public void testMatch(){
        SearchQuery query = new NativeSearchQuery(QueryBuilders.matchQuery("title", "华为mate40"));
        List<Item> itemList = restTemplate.queryForList(query, Item.class);
        for(Item item : itemList){
            System.out.println(item);
        }
    }

    /**
     * 搜索所有数据
     */
    @Test
    public void testMatchAll(){
        /*
         * SearchQuery - 是Spring Data Elasticsearch中定义的一个搜索接口
         * NativeSearchQuery - 是SearchQuery接口的实现类。
         *  构造的时候,需要提供一个QueryBuilder类型的对象,
         *  QueryBuilder是Elasticsearch的java客户端中定义的搜索条件类型。
         *
         * QueryBuilders - 是QueryBuilder类型的工具类,可以快速实现QueryBuilder类型对象的创建
         *  工具类中,提供了大量的静态方法,方法命名和DSL搜索中的条件关键字相关。
         *  如:match_all 对应 matchAllQuery()
         *  如:match 对应 matchQuery()
         *  如:range 对应 rangeQuery()
         */
        SearchQuery query = new NativeSearchQuery(
                QueryBuilders.matchAllQuery()
        );
        List<Item> itemList = restTemplate.queryForList(query, Item.class);
        for(Item item : itemList){
            System.out.println(item);
        }
    }


    /**
     * 修改文档
     * 如果是全量替换,可以使用index方法实现,只要主键在索引中存在,就是全量替换。<=>新增or批量新增操作
     * 如果是部分修改,则可以使用update实现。
     */
    @Test
    public void testUpdate() throws Exception{
        UpdateRequest request = new UpdateRequest();
        request.doc(
                XContentFactory.jsonBuilder()
                        .startObject()
                        .field("name", "测试update更新数据,商品名称")
                        .endObject()
        );
        UpdateQuery updateQuery =
                new UpdateQueryBuilder()
                        .withUpdateRequest(request)
                        .withClass(Item.class)
                        .withId("20210224")
                        .build();
        restTemplate.update(updateQuery);
    }

    /**
     * 删除文档
     */
    @Test
    public void testDelete(){
        // 根据主键删除, 常用
        String result = restTemplate.delete(Item.class, "192168111");
        System.out.println(result);

        // 根据查询结果,删除查到的数据。 应用较少。
        /*DeleteQuery query = new DeleteQuery();
        query.setIndex("hrt-item");
        query.setType("item");
        query.setQuery(QueryBuilders.matchQuery("title", "沃什.伊戈.史莱姆1"));
        restTemplate.delete(query, Item.class);*/
    }

    /**
     * 批量新增
     * bulk操作
     */
    @Test
    public void testBatchInsert(){
        List<IndexQuery> queries = new ArrayList<IndexQuery>();
        Item item = new Item();
        item.setId("20210224");
        item.setTitle("IPHONE 12 手机");
        item.setSellPoint("很贵");
        item.setPrice(499900L);
        item.setNum(999);
        queries.add(new IndexQueryBuilder().withObject(item).build());
        item = new Item();
        item.setId("20210225");
        item.setTitle("华为P40照相手机");
        item.setSellPoint("可以拍月亮");
        item.setPrice(599900L);
        item.setNum(999);
        queries.add(new IndexQueryBuilder().withObject(item).build());
        item = new Item();
        item.setId("20210226");
        item.setTitle("红米k30");
        item.setSellPoint("大品牌值得信赖");
        item.setPrice(699900L);
        item.setNum(999);
        queries.add(new IndexQueryBuilder().withObject(item).build());
        // 批量新增,使用的是bulk操作。
        restTemplate.bulkIndex(queries);
    }

    /**
     * 新增数据到ES
     */
    @Test
    public void testInsert(){
        Item item = new Item();
        item.setId("19216811");
        item.setTitle("沃什.伊戈.史莱姆");
        item.setSellPoint("关于我成为史莱姆却因为铺垫太长遭人骂这档事");
        item.setPrice(996666L);
        item.setNum(233);

        //这个是构建器放入的方式
        IndexQuery indexQuery = new IndexQueryBuilder() // 创建一个IndexQuery的构建器
                                    .withObject(item) // 设置要新增的Java对象
                                    .build(); // 构建IndexQuery类型的对象。
        //这个是直接放入的方式
//        IndexQuery query = new IndexQuery();
//        query.setObject(item);
        // index逻辑,相当于使用PUT请求,实现数据的新增。
        String result = restTemplate.index(indexQuery);
        System.out.println(result);
    }

    /**
     * 删除索引
     */
    @Test
    public void deleteIndex(){
        // 扫描Item类型上的Document注解,删除对应的索引。
        boolean isDeleted = restTemplate.deleteIndex(Item.class);
        System.out.println("删除Item对应索引是否成功:" + isDeleted);
        // 直接删除对应名称的索引。
        isDeleted = restTemplate.deleteIndex("test_index3");
        System.out.println("删除default_index索引是否成功:" + isDeleted);
    }


    /**
     * 创建索引,并设置映射。
     * 需要通过两次访问实现,1、创建索引;2、设置映射。
     */
    @Test
    public void testInitIndex(){
        // 创建索引,根据类型上的Document注解创建
        boolean isCreated = restTemplate.createIndex(Item.class);
        // 设置映射,根据属性上的Field注解设置 0201
        boolean isMapped = restTemplate.putMapping(Item.class);
        System.out.println("创建索引是否成功:" + isCreated);
        System.out.println("设置映射是否成功:" + isMapped);
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时间静止不是简史

感谢你的肯定, 我将继续努力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值