SpringBoot2.2.0.RELEASE整合Elasticsearch6.8.3

SpringBoot2.2.0.RELEASE整合Elasticsearch6.8.3

SpringBoot是2.2.0.RELEASE,elasticsearch是6.8.3

使用依赖spring-boot-starter-data-elasticsearch

使用ElasticSearchRepository操作

1、导入依赖

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.0.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>spring-boot-elasticsearch5</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-elasticsearch5</name>
    <description>spring-boot-elasticsearch5</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2、配置文件

spring:
  elasticsearch:
    rest:
      uris: http://192.168.94.186:9200

3、创建索引的实体类

package com.example.search.entity;

import lombok.*;
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;

import java.io.Serializable;
import java.util.Map;

/**
 * 1.创建索引
 * 2.创建类型
 * 3.创建文档
 * 4.字段的映射(是否分词,是否索引,是否存储,数据类型是什么,分词器是什么)
 * indexName 指定创建的索引的名称
 * type :指定索引中的类型
 */

@Getter
@Setter
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "shop_info", type = "docs")
public class ShopInfo implements Serializable {
    
    

    @Id
    @Field(type = FieldType.Text)
    private Long id;

    @Field(type = FieldType.Text, analyzer = "ik_smart")
    private String name;

    @Field(type = FieldType.Double)
    private Double price;

    @Field(type = FieldType.Keyword)
    private String categoryName;

    @Field(type = FieldType.Keyword)
    private String brandName;

    @Field(type = FieldType.Keyword)
    private String spec;

    // -ES能够自动存储未提交创建字段信息的数据
    // 目的:未指定时ES为了可以更好的支持聚合和查询功能,所以默认创建了两种
    // 对于未提前指定类型的字段,使用以下默认规则
    //  [字段](text)   		  #分词不聚合
    //  [字段].keyword(keyword) #聚合不分词
    private Map<String, Object> specMap;

}
package com.example.search.entity;

import lombok.*;

import java.util.Map;

/**
 * 查询数据的封装
 */
@Getter
@Setter
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ShopVO {
    
    

    private Long id;

    private String name;

    private Double price;

    private String categoryName;

    private String brandName;

    private String spec;

    private Map<String, Object> specMap;
}

4、ShopEsMapper

package com.example.search.dao;

import com.example.search.entity.ShopInfo;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Component;

@Component
public interface ShopEsMapper extends ElasticsearchRepository<ShopInfo, Long> {
    
    
}

5、Service

package com.example.search.service;

import java.util.Map;

public interface ShopSearchService {
    
    

    /**
     * 1.查询符合条件的shop的数据
     * 2.调用spring data elasticsearch的API导入到ES中
     */
    void importEs();

    /**
     * 进行查询
     *
     * @param searchMap
     * @return
     */
    Map search(Map<String, String> searchMap);

    /**
     * 创建索引
     */
    boolean createIndex();

    /**
     * 删除索引
     */
    boolean deleteIndex();

}
package com.example.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.example.search.dao.ShopEsMapper;
import com.example.search.entity.ShopInfo;
import com.example.search.entity.ShopVO;
import com.example.search.service.ShopSearchService;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;

@Service
public class ShopSearchServiceImpl implements ShopSearchService {
    
    

    @Autowired
    private ShopEsMapper shopEsMapper;

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Override
    public void importEs() {
    
    
        // 1.查询符合条件的shop的数据
        // 这里正常是从数据库中查询数据
        // 现在只是测试,只添加两条数据
        List<ShopVO> shopVOList = new ArrayList<>();
        Map<String, Object> specMap = new HashMap<>();
        specMap.put("颜色", "白色");
        specMap.put("内存", "64G");
        specMap.put("硬盘", "1T");
        specMap.put("待机", "8h");
        shopVOList.add(new ShopVO(1L, "华为手机", 2000.0, "手机", "华为", "{\"内存\":\"64G\",\"颜色\":\"白色\"}", specMap));
        shopVOList.add(new ShopVO(2L, "小米电脑", 3000.0, "电脑", "小米", "{\"硬盘\":\"1T\",\"颜色\":\"金色\"}", specMap));
        // 将shopVO的列表转换成es中的ShopInfo的列表
        List<ShopInfo> shopInfoList = JSON.parseArray(JSON.toJSONString(shopVOList), ShopInfo.class);
        // 2.调用spring data elasticsearch的API导入到ES中
        shopEsMapper.saveAll(shopInfoList);
    }

    /**
     * @param searchMap
     */
    @Override
    public Map search(Map<String, String> searchMap) {
    
    
        // 1.获取到关键字
        String keywords = searchMap.get("keywords");
        // 2.判断是否为空,如果为空给一个默认值:华为
        // 查询所有
        // SELECT * FROM shop WHERE name LIKE '%手机%';
        if (StringUtils.isEmpty(keywords)) {
    
    
            keywords = "华为";
        }
        // 3.创建查询构建对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        // 4.设置查询的条件
        // SELECT categoryName FROM shop WHERE name LIKE '%手机%' GROUP BY categoryName;
        // 4.1商品分类的列表展示: 按照商品分类的名称来分组
        // terms:指定分组的一个别名
        // field:指定要分组的字段名
        // size:指定查询结果的数量,默认是10个
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("shopCategoryGroup").field("categoryName.keyword").size(50));
        // 4.2商品的品牌的列表展示,按照商品品牌来进行分组
        // SELECT brandName FROM shop WHERE name LIKE '%手机%' GROUP BY brandName;
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("shopBrandGroup").field("brandName.keyword").size(100));
        // 4.3商品的规格的列表展示,按照商品的规格的字段spec进行分组
        // SELECT spec FROM shop WHERE name LIKE '%手机%' GROUP BY spec;
        // 规则要求字段是一个keyword类型的,spec.keyword
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("shopSpecGroup").field("spec.keyword").size(500));
        // 4.4设置高亮的字段,设置前缀和后缀
        // 设置高亮的字段,针对商品的名称进行高亮
        nativeSearchQueryBuilder.withHighlightFields(new HighlightBuilder.Field("name"));
        // 设置前缀和后缀
        nativeSearchQueryBuilder.withHighlightBuilder(new HighlightBuilder().preTags("<em style=\"color:red\">").postTags("</em>"));
        // 匹配查询:先分词,再查询,主条件查询
        // 参数1:指定要搜索的字段
        // 参数2:要搜索的值(先分词,再搜索)
        // 从单个字段搜索数据
        // nativeSearchQueryBuilder.withQuery(QueryBuilders.matchQuery("name", keywords));
        // 从多个字段中搜索数据,参数1为关键字,后面的参数为所有的字段
        nativeSearchQueryBuilder.withQuery(QueryBuilders.multiMatchQuery(keywords, "name", "categoryName", "brandName"));
        //========================过滤查询开始=====================================
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 4.4 过滤查询的条件设置
        // 商品分类的条件
        String category = searchMap.get("category");
        if (!StringUtils.isEmpty(category)) {
    
    
            boolQueryBuilder.filter(QueryBuilders.termQuery("categoryName", category));
        }
        // 4.5 过滤查询的条件设置
        // 商品品牌的条件
        String brand = searchMap.get("brand");
        if (!StringUtils.isEmpty(brand)) {
    
    
            boolQueryBuilder.filter(QueryBuilders.termQuery("brandName", brand));
        }
        //4.6 过滤查询的条件设置
        // 规格条件
        if (searchMap != null) {
    
    
            //{ spec_网络:"电信4G",spec_顔色:"黑色"}
            for (String key : searchMap.keySet()) {
    
    
                if (key.startsWith("spec_")) {
    
    
                    //截取规格的名称
                    boolQueryBuilder.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", searchMap.get(key)));
                }
            }
        }
        // 4.7 过滤查询的条件设置
        // 价格区间的过滤查询
        // 0-500  3000-*
        String price = searchMap.get("price");
        if (!StringUtils.isEmpty(price)) {
    
    
            //获取值 按照- 切割
            String[] split = price.split("-");
            //过滤范围查询
            //0<=price<=500
            if (!split[1].equals("*")) {
    
    
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").from(split[0], true).to(split[1], true));
            } else {
    
    
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(split[0]));
            }
        }
        //过滤查询
        nativeSearchQueryBuilder.withFilter(boolQueryBuilder);
        //========================过滤查询结束=====================================
        // 分页查询
        // 第一个参数:指定当前的页码  注意: 如果是第一页 数值为0
        // 第二个参数:指定当前的页的显示的行
        String pageNum1 = searchMap.get("pageNum");
        Integer pageNum = Integer.valueOf(pageNum1);
        String pageSize1 = searchMap.get("pageSize");
        Integer pageSize = Integer.valueOf(pageSize1);
        nativeSearchQueryBuilder.withPageable(PageRequest.of(pageNum - 1, pageSize));
        // 排序操作
        // 获取排序的字段 和要排序的规则
        // price
        String sortField = searchMap.get("sortField");
        // DESC ASC
        String sortRule = searchMap.get("sortRule");
        if (!StringUtils.isEmpty(sortField) && !StringUtils.isEmpty(sortRule)) {
    
    
            // 执行排序
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField).order(sortRule.equalsIgnoreCase("ASC") ? SortOrder.ASC : SortOrder.DESC));
            // nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField).order(SortOrder.valueOf(sortRule)));
        }
        // 5.构建查询对象(封装了查询的语法)
        NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();
        //6.执行查询
        AggregatedPage<ShopInfo> shopInfos = elasticsearchRestTemplate.queryForPage(nativeSearchQuery, ShopInfo.class, new SearchResultMapperImpl());
        // 6.2 获取聚合分组结果  获取商品分类的列表数据
        Terms stringTermsCategory = (Terms) shopInfos.getAggregation("shopCategoryGroup");
        List<String> categoryList = getStringsCategoryList(stringTermsCategory);
        //6.3 获取 品牌分组结果 列表数据
        Terms stringTermsBrand = (Terms) shopInfos.getAggregation("shopBrandGroup");
        List<String> brandList = getStringsBrandList(stringTermsBrand);
        //6.4 获取 规格的分组结果 列表数据map
        Terms stringTermsSpec = (Terms) shopInfos.getAggregation("shopSpecGroup");
        Map<String, Set<String>> specMap = getStringSetMap(stringTermsSpec);
        //7.获取结果
        // 返回map
        //当前的页的集合
        List<ShopInfo> content = shopInfos.getContent();
        //总页数
        int totalPages = shopInfos.getTotalPages();
        //总记录数
        long totalElements = shopInfos.getTotalElements();
        Map<String, Object> resultMap = new HashMap<>();
        //商品分类的列表数据
        resultMap.put("categoryList", categoryList);
        //商品品牌的列表数据
        resultMap.put("brandList", brandList);
        //商品规格的列表数据展示
        resultMap.put("specMap", specMap);
        resultMap.put("rows", content);
        resultMap.put("total", totalElements);
        resultMap.put("totalPages", totalPages);
        resultMap.put("pageNum", pageNum);
        resultMap.put("pageSize", pageSize);
        return resultMap;
    }

    @Override
    public boolean createIndex() {
    
    
        // 创建索引,会根据ShopInfo类的@Document注解信息来创建
        Boolean aBoolean = elasticsearchRestTemplate.createIndex(ShopInfo.class);
        // 配置映射,会根据ShopInfo类中的id、Field等字段来自动完成映射
        Boolean aBoolean1 = elasticsearchRestTemplate.putMapping(ShopInfo.class);
        System.out.println("创建索引是否成功:" + (aBoolean && aBoolean1));
        return aBoolean && aBoolean1;
    }

    @Override
    public boolean deleteIndex() {
    
    
        Boolean aBoolean = elasticsearchRestTemplate.deleteIndex(ShopInfo.class);
        System.out.println("删除索引是否成功:" + aBoolean);
        return aBoolean;
    }

    private Map<String, Set<String>> getStringSetMap(Terms stringTermsSpec) {
    
    
        // key :规格的名称
        // value :规格名称对应的选项的多个值集合set
        Map<String, Set<String>> specMap = new HashMap<String, Set<String>>();
        Set<String> specValues = new HashSet<String>();
        if (stringTermsSpec != null) {
    
    
            // 1. 获取分组的结果集
            for (Terms.Bucket bucket : stringTermsSpec.getBuckets()) {
    
    
                //2.去除结果集的每一行数据()
                // {"手机屏幕尺寸":"5.5寸","网络":"电信4G","颜色":"白","测试":"s11","机身内存":"128G","存储":"16G","像素":"300万像素"}
                String keyAsString = bucket.getKeyAsString();
                System.out.println("keyAsString:" + keyAsString);
                //3.转成JSON 对象  map  key :规格的名称  value:规格名对应的选项的单个值
                Map<String, String> map = JSON.parseObject(keyAsString, Map.class);
                for (Map.Entry<String, String> stringStringEntry : map.entrySet()) {
    
    
                    //规格名称:手机屏幕尺寸
                    String key = stringStringEntry.getKey();
                    //规格的名称对应的单个选项值 5.5寸
                    String value = stringStringEntry.getValue();
                    //先从原来的specMap中 获取 某一个规格名称 对应的规格的选项值集合
                    specValues = specMap.get(key);
                    if (specValues == null) {
    
    
                        specValues = new HashSet<>();
                    }
                    specValues.add(value);
                    //4.提取map中的值放入到返回的map中
                    specMap.put(key, specValues);
                }
            }
        }
        return specMap;
    }

    private List<String> getStringsBrandList(Terms stringTermsBrand) {
    
    
        List<String> brandList = new ArrayList<>();
        if (stringTermsBrand != null) {
    
    
            for (Terms.Bucket bucket : stringTermsBrand.getBuckets()) {
    
    
                //品牌的名称 huawei
                String keyAsString = bucket.getKeyAsString();
                brandList.add(keyAsString);
            }
        }
        return brandList;
    }

    /**
     * 获取分组结果   商品分类的分组结果
     *
     * @param stringTermsCategory
     * @return
     */
    private List<String> getStringsCategoryList(Terms stringTermsCategory) {
    
    
        List<String> categoryList = new ArrayList<>();
        if (stringTermsCategory != null) {
    
    
            for (Terms.Bucket bucket : stringTermsCategory.getBuckets()) {
    
    
                String keyAsString = bucket.getKeyAsString();
                //就是商品分类的数据
                categoryList.add(keyAsString);
            }
        }
        return categoryList;
    }

}
package com.example.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.example.search.entity.ShopInfo;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
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;
import java.util.Map;

/**
 * 自定义结果集映射 ()
 * 目的: 获取高亮的数据
 */

public class SearchResultMapperImpl implements SearchResultMapper {
    
    

    @Override
    public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
    
    

        //1.创建一个当前页的记录集合对象
        List<T> content = new ArrayList<>();

        if (response.getHits() == null || response.getHits().getTotalHits() <= 0) {
    
    
            return new AggregatedPageImpl<T>(content);
        }

        //搜索到的结果集
        for (SearchHit searchHit : response.getHits()) {
    
    
            //每一个行的数据 json的 数据
            String sourceAsString = searchHit.getSourceAsString();
            ShopInfo skuInfo = JSON.parseObject(sourceAsString, ShopInfo.class);
            //key :高亮的字段名  value 就是该字段的高亮的数据集合
            Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
            HighlightField highlightField = highlightFields.get("name");
            //有高亮的数据
            if (highlightField != null) {
    
    
                //有高亮的数据
                StringBuffer buffer = new StringBuffer();
                //取高亮的数据
                for (Text text : highlightField.getFragments()) {
    
    
                    //高亮的数据  华为 胀奸  5寸  联通2G  白  <em style='color=red>'显示</em>  32G  16G  300万像素
                    String string = text.string();
                    buffer.append(string);
                }
                //有高亮的数据
                skuInfo.setName(buffer.toString());
            }
            content.add((T) skuInfo);

        }
        //2.创建分页的对象 已有
        //3.获取总个记录数
        long totalHits = response.getHits().getTotalHits();
        //4.获取所有聚合函数的结果
        Aggregations aggregations = response.getAggregations();
        //5.深度分页的ID
        String scrollId = response.getScrollId();
        return new AggregatedPageImpl<T>(content, pageable, totalHits, aggregations, scrollId);
    }

    @Override
    public <T> T mapSearchHit(SearchHit searchHit, Class<T> aClass) {
    
    
        return null;
    }
}

6、启动类

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;

@SpringBootApplication
@EnableElasticsearchRepositories(basePackages = "com.example.search.dao")
public class SpringBootElasticsearch5Application {
    
    

    public static void main(String[] args) {
    
    

        SpringApplication.run(SpringBootElasticsearch5Application.class, args);
    }

}

7、测试

package com.example;

import com.example.search.entity.ShopInfo;
import com.example.search.service.ShopSearchService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@SpringBootTest
class SpringBootElasticsearch5ApplicationTests {
    
    

    @Autowired
    private ShopSearchService shopSearchService;

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Test
    void createIndex() {
    
    
        shopSearchService.createIndex();
    }

    @Test
    void deleteIndex() {
    
    
        shopSearchService.deleteIndex();
    }

    @Test
    void saveData() {
    
    
        // 如果没有索引在执行的时候自动会创建索引
        shopSearchService.importEs();
    }

    /**
     * 参数有:
     * keywords
     * category
     * brand
     * spec_
     * price
     * pageNum
     * pageSize
     * sortField
     * sortRule
     */
    @Test
    void search() {
    
    
        Map<String, String> map = new HashMap<>();
        map.put("pageNum", "1");
        map.put("pageSize", "1");
        Map resultMap = shopSearchService.search(map);
        log.info(resultMap.toString());
    }

}

7.1 插入数据测试

插入数据前不需要先建立索引,在执行插入的时候会自动建立。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

7.2 搜索测试

在这里插入图片描述

2022-06-24 15:06:07.992  INFO 15828 --- [           main] SpringBootElasticsearch5ApplicationTests : {
    
    total=1, categoryList=[手机], totalPages=1, specMap={
    
    颜色=[白色], 内存=[64G]}, pageSize=1, brandList=[华为], rows=[ShopInfo(id=1, name=<em style="color:red"></em><em style="color:red"></em>手机, price=2000.0, categoryName=手机, brandName=华为, spec={
    
    "内存":"64G","颜色":"白色"}, specMap={
    
    硬盘=1T, 颜色=白色, 内存=64G, 待机=8h})], pageNum=1}

猜你喜欢

转载自blog.csdn.net/qq_30614345/article/details/131994833
今日推荐