在项目开发中使用ElasticSearch

为什么要在项目中使用

搜索功能会产生巨大的资源消耗,沉重的数据库加载会拖垮整个应用性能,所以我们将搜索功能转移到一个外部的搜索服务器,减轻数据库的压力。es是基于Lucene(全文检索引擎工具包)的高性能搜索服务。使用于高并发的搜索应用程序。

  • 在项目中使用 Spring Data ElasticSearch
  • 简化ES的java客户端开发,底层封装了ES的客户端官方API

倒排索引

倒排索引:是搜索引擎的核心。由于正排索引的检索耗时太长,所以一般我们使用的搜索引擎都为倒排索引,是以word(关键字)作为索引。其实就是建立词语和文档的对应关系,词语哪篇文档中出现,在哪个位置出现,出现了几次。
在这里插入图片描述
详见:https://blog.csdn.net/zhanggaokai/article/details/76563206

ElasticSearch的优点

  1. 近乎实时的存储、检索数据。
  2. 扩展性好,可拓展至上百台服务器。
  3. 处理数据量大。

对比solr

solrEs
利用zookeeper进行分布式管理自带分布式协调管理功能
支持的格式多JSON格式
自动功能多需通过第三方插件
适用于传统搜索应用使用于实时搜索应用

在这里插入图片描述

为什么不使用solr

  1. solr在传统的搜索应用中性能高于ES,但在处理实时搜索时的效率低于ES;
  2. 对已有数据搜索时,solr更快,但在建立索引时,solr会产生IO阻塞,查询性能差;
  3. 随着数据量的增加,solr的搜索效率会减低,ES不变。

关系型数据库和ES的对应关系

关系型数据库数据库表(table)行(rows)列(columns)
ES服务索引库类型(types)文档(document)字段(fields)

安装部署ElasticSearch,集成分词器(略)

  • 参考网络文档

依赖jar包

  <dependency>
         <groupId>org.springframework.data</groupId>
         <artifactId>spring-data-elasticsearch</artifactId>
         <version>3.1.3.RELEASE</version>
    </dependency>

SpringDataElasticSearch注解

  • @Document : 将实体类映射成一篇文档
    iddexName 指定索引库名称
    type 指定索引库的类型(类似数据库中的表)

  • @Id : 主键

  • @Field : 将数据库表中的列转化成文档中的Field
    store: 是否存储 true存储|false不存储(默认为false)
    index: 是否建立索引true建立|false不建立(默认为true)
    type: 指定该Field的数据类型
    analyzer: 指定建立索引时的分词器
    searchAnalyzer: 指定搜索时的分词器
    copyTo: 复制到哪个Field(将多Field复制到一个Field,方便指定搜索条件)
    pattern: 日期类型,需要指定日期格式器

Spring整合ES

  • 创建数据表对应实体类,用注解方式进行映射(略)
  • 配置SpringDataElasticsearch.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/data/elasticsearch
       http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd">

    <!-- 2.配置Elasticsearch客户端连接对象
        cluster-nodes: 集群中的节点,如果有多个节点用逗号隔开
        cluster-name: 集群的名称
     -->
    <elasticsearch:transport-client id="client"   cluster-nodes="192.168.12.131:9300"
     cluster-name="elasticsearch"/>

    <!-- 1.配置ElasticsearchTemplate模板对象 -->
    <bean id="elasticsearchTemplate"
          class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
        <!-- 设置客户端连接对象 -->
        <constructor-arg name="client" ref="client"/>
    </bean>

    <!-- 3.配置Elasticsearch数据访问接口,采用包扫描 -->
    <elasticsearch:repositories base-package="cn.uda.es.dao"/>
</beans>
  • 创建数据访问接口
//需继承ElasticsearchRepository
public interface ManDao extends ElasticsearchRepository<实体类名称,主键>{

}

基本使用(CRUD)

  • CRUD使用数据访问接口
  • 搜索使用elasticsearchTemplate对象(已在配置文件中创建)

添加/修改:save(对象);

public void testSave(){
    Man man= new Man();
    man.setName("UDA");
    man.setAge(24);
    // 添加或修改类型
    itemDao.save(man);
}
  • 注:无需先创建索引库,添加映射,在进行save操作时会进行判断,如果没有索引库和映射会自动添加。

批量添加:ManDao .saveAll(对象集合)

public void testBatchSave(){
    List<Man> manList = new ArrayList<>();
    for (long i = 1; i <= 1000; i++) {
        Man man= new man();
        man.setId(i);
        man.setName("UDA" + i);
    }
    // 批量添加或修改类型
    ManDao.saveAll(manList);
}

删除

// 根据主键ID删除:deleteById("id");
@Test
public void testDeleteById(){
    manDao.deleteById(1);
}
//根据条件删除:delete(对象);
@Test
public void testDeleteByItem(){
    Man man = new man();
    man.setId(2);
    manDao.delete(man);
}
//删除全部:deleteAll();
@Test
public void testDeleteAll(){
    manDao.deleteAll();
}

查询

//根据ID查询:findById("Id");
public void testFindById(){
    Man man = manDao.findById(1).get();
    System.out.println(man.getId() + "   " + man.getName());
}
//查询全部:findAll();
public void testFindAll(){
    Iterable<man> mans = manDao.findAll();
    for (Item item : items) {
        System.out.println(item.getId() + "\t" + item.getTitle());
    }
}
//分页查询:findAll(Pageable);
public void testFindByPage(){
    // 创建分页参数封装对象(当前页面,每页大小)
    Pageable pageable = PageRequest.of(0, 5);
    Page<Man> page = imanDao.findAll(pageable);
    List<Man> items = page.getContent();
    for (Man man: mans) {
        System.out.println(man.getId());
    }
}

使用ES进行搜索

  • 默认十条数据一页
  • 使用NativeSearchQuery原生搜索查询对象
    创建搜索查询对象SearchQuery
  • wildcard 不分词
  • matchQuery分词
public void testWildcardQuery(){
    // 创建搜索查询对象 (wildcardQuery: 不分词
    SearchQuery query = new NativeSearchQuery(QueryBuilders.wildcardQuery("title", "华   为"));
    // 分页搜索,得到合计分页对象
    AggregatedPage<Item> page = esTemplate.queryForPage(query, Item.class);
    System.out.println("总记录数: " + page.getTotalElements());
    System.out.println("总页数: " + page.getTotalPages());
    // 获取分页数据
    List<Item> items = page.getContent();
    for (Item item : items) {
        System.out.println(item.getId() + "\t" + item.getTitle());
    }
}




/** 匹配分页搜索 */
public void testMatchQuery(){
    /**
    * 创建搜索查询对象 (matchQuery: 分词)
    * keywords 搜索fileds
    * 华为小米 搜索关键字
     */
    SearchQuery query = new NativeSearchQuery(QueryBuilders.matchQuery("keywords", "华为小米"));
    // 创建分页参数封装对象
    Pageable pageable = PageRequest.of(0, 5);
    // 设置分页
    query.setPageable(pageable);
    // 分页搜索
    AggregatedPage<Item> page = esTemplate.queryForPage(query, Item.class);
    System.out.println("总记录数: " + page.getTotalElements());
    System.out.println("总页数: " + page.getTotalPages());
    // 获取分页数据
    List<Item> items = page.getContent();
    for (Item item : items) {
        System.out.println(item.getId() + "\t" + item.getTitle());
    }

复制fields

在这里插入图片描述

嵌套field

  • 只能使用Map集合进行封装

项目中使用步骤

  1. 查询数据库查询所有商品数据
  2. 创建实体类,加上注解
  3. 导入依赖jar包
  4. 配置文件
  5. 编写数据访问接口

项目中controller编写

  • 在controller中不使用原生搜索条件对象NativeSearchQuery创建搜索查询对象SearchQuery 。
  • 使用原生搜索条件构建对象NativeSearchQueryBuilder创建搜索查询对象SearchQuery ,方便添加查询条件。
  • wildcard 不分词
  • matchQuery 分词
  • matchAllQuery 查询全部(分词)
  • multiMatchQuery 设置高亮时使用
/** 商品搜索服务接口实现类 */
@Service(interfaceName = "com.pinyougou.service.ItemSearchService")
public class ItemSearchServiceImpl implements ItemSearchService {
    @Autowired
    private ElasticsearchTemplate esTemplate;
    @Autowired
    private EsItemDao esItemDao;

    /** 搜索方法 */
    @Override
    public Map<String, Object> search(Map<String,Object> params) {

        /** 获取查询关键字 */
        String keywords = (String)params.get("keywords");

        // 创建原生的搜索条件构建对象
         NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
        // 设置默认搜索全部
        builder.withQuery(QueryBuilders.matchAllQuery());

        /** 判断检索关键字是否为空 */
        if (StringUtils.isNoneBlank(keywords)) {
            // 设置根据条件匹配查询: keywords 复制Field
            builder.withQuery(QueryBuilders .matchQuery("keywords", keywords));
        }

        // 构建搜索查询对象
        SearchQuery query = builder.build();
        // 分页查询,得到合计分页对象
        AggregatedPage<EsItem> page = esTemplate
                .queryForPage(query, EsItem.class);

        // 定义Map集合封装返回数据
        Map<String,Object> data = new HashMap<>();
        // 设置总记录数
        data.put("total", page.getTotalElements());
        // 设置分页结果
        data.put("rows", page.getContent());
        return data;
    }
}

高亮字段显示

高亮设置

  1. 创建高亮字段对象
    HighlightBuilder.Field
  2. 设置高亮字段(使用原生搜索查询构建对象设置高亮字段)
    builder.withHighlightFields(高亮字段对象);
  3. 在合计分页对象AggregatePage中转化搜索结果SearchResultMapper
AggregatedPage<EsItem> page = esTemplate.queryForPage(query, EsItem.class, new SearchResultMapper() {...}
  • 高亮字段设置不能使用复制field
/** 搜索方法 */
@Override
public Map<String, Object> search(Map<String,Object> params) {
    /** 获取查询关键字 */
    String keywords = (String)params.get("keywords");

    // 创建原生的搜索条件构建对象
    NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
    // 设置默认搜索全部
    builder.withQuery(QueryBuilders.matchAllQuery());

    /** 判断检索关键字是否为空 */
    if (StringUtils.isNoneBlank(keywords)) {
        // 设置根据多条件匹配查询:
        builder.withQuery(QueryBuilders.multiMatchQuery(keywords,
                "title", "category", "brand", "seller"));

        // 创建高亮字段对象
        HighlightBuilder.Field field = new HighlightBuilder
                .Field("title") // 设置需要高亮的字段
                .preTags("<font color='red'>") // 设置高亮前缀
                .postTags("</font>") // 设置高亮后缀
                .fragmentSize(50); // 设置文本截断
        // 设置高亮字段对象
        builder.withHighlightFields(field);
    }

    // 构建搜索查询对象
    SearchQuery query = builder.build();

    // 分页查询,得到合计分页对象
    AggregatedPage<EsItem> page = esTemplate.queryForPage(query, EsItem.class,
                    new SearchResultMapper() { // 搜索结果转化
          @Override
        public <T> AggregatedPage<T> mapResults(SearchResponse sr,
                         Class<T> aClass, Pageable pageable) {
               // 定义List集合封装分页结果
            List<T> content = new ArrayList<>();
            // 循环封装分页结果
            for (SearchHit hit : sr.getHits()) {
             // 获取搜索的文档json字符串,转化成EsItem对象
                EsItem esItem = JSON.parseObject(hit.getSourceAsString(), EsItem.class);
                // 获取标题高亮对象
                HighlightField highlightTitle = hit.getHighlightFields().get("title");
                // 判断是否有标题高亮对象
             if (highlightTitle != null){
                 // 获取标题高亮内容字符串
                 String title = highlightTitle.getFragments()[0].toString();
                 // 设置标题高亮内容
                 esItem.setTitle(title);
             }
             // 添加到新的集合
             content.add((T)esItem);
            }
            return new AggregatedPageImpl<T>(content,
                    pageable, sr.getHits().getTotalHits());
        }
    });

    // 定义Map集合封装返回数据
    Map<String,Object> data = new HashMap<>();
    // 设置总记录数
    data.put("total", page.getTotalElements());
    // 设置分页结果
    data.put("rows", page.getContent());
    return data;
}
  • 前端数据使用v-html即可完成高亮显示

组合查询对象

  • 原生搜索查询对象添加过滤查询builder.withFilter(组合查询构建对象);
  • 组合查询构建对象 BoolQueryBuilder(must必要条件)
  • 范围查询构建对象 RangQueryBuilder
public Map<String, Object> search(Map<String,Object> params) {
    /** 获取查询关键字 */
    String keywords = (String)params.get("keywords");
    ......
    /** 判断检索关键字是否为空 */
    if (StringUtils.isNoneBlank(keywords)) {
        // 设置根据多条件匹配查询
        // 创建高亮字段对象
......
    }
    /**  ############# 过滤查询 ########### */
    // 1. 创建组合查询构建对象,封装多个过滤条件
    BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery();

    // 2. 组合多个过滤条件(必须的方式组合)
    // 2.1 按商品分类过滤
    String category = (String)params.get("category");
    if (StringUtils.isNoneBlank(category)){
        // 词条查询
        boolBuilder.must(QueryBuilders.termQuery("category", category));
    }
    // 2.2 按商品品牌过滤
    String brand = (String)params.get("brand");
    if (StringUtils.isNoneBlank(brand)){
        // 词条查询
        boolBuilder.must(QueryBuilders.termQuery("brand", brand));
    }
    // 2.3 按商品规格过滤
    Map<String,String> specMap = (Map<String, String>)params.get("spec");
    if (specMap != null && specMap.size() > 0){
        // 迭代Map集合
        for (String key : specMap.keySet()) {
            // 嵌套Field的名称
            String field = "spec." + key + ".keyword";
            // 嵌套查询
            boolBuilder.must(QueryBuilders.nestedQuery("spec",
                    QueryBuilders.termQuery(field, specMap.get(key)),
                    ScoreMode.Max));
        }
    }
    // 2.4 按商品价格区间过滤
    String price = (String)params.get("price");
    if (StringUtils.isNoneBlank(price)) {
        // 得到价格区间数组
        String[] priceArr = price.split("-");
        // 范围查询构建对象
        RangeQueryBuilder rqBuilder = QueryBuilders.rangeQuery("price");
        // 如果价格结束为星号
        if ("*".equals(priceArr[1])){
            // 大于起始价格 3000-*
            rqBuilder.gt(priceArr[0]);
        }else{
            // 从起始价格 到 结束价格
            rqBuilder.from(priceArr[0]).to(priceArr[1]);
        }
        // 组合范围查询
        boolBuilder.must(rqBuilder);
    }

    // 3. 原生搜索查询对象添加过滤查询
    builder.withFilter(boolBuilder);

    // 构建搜索查询对象
    SearchQuery query = builder.build();
    // 分页查询,得到合计分页对象
......
    return data;
}

搜索分页

分页使用搜索查询对象

  • 搜索分页使用搜索查询对象
    NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
  • 创建搜索查询对象
    SearchQuery query = builder.build();
  • 添加分页条件
    query.setPageable(PageRequest.of(curPage - 1, 20));
/**  ############# 分页查询 ########### */
    // 1. 获取当前页面
    Integer curPage = (Integer) params.get("page");
    if (curPage == null){
        curPage = 1;
    }
    // 2. 设置分页对象 (注意,页码从0开始)
    query.setPageable(PageRequest.of(curPage - 1, 20));

    // 分页查询,得到合计分页对象
    AggregatedPage<EsItem> page = esTemplate.queryForPage(query,  
EsItem.class,new SearchResultMapper() {......});
    // 定义Map集合封装返回数据
    Map<String,Object> data = new HashMap<>();
    // 设置总记录数
    data.put("total", page.getTotalElements());
    // 设置分页结果
    data.put("rows", page.getContent());
    // 设置总页数
    data.put("totalPages", page.getTotalPages());
    return data;

搜索排序

搜索排序使用搜索查询对象

  • 创建排序对象
    Sort sort = new Sort(升序(枚举)|降序(枚举));
  • 搜索查询对象添加排序
    query.addSort(sort);
    // 1. 获取排序参数
    String sortValue = (String) params.get("sortValue"); // ASC DESC
    String sortField = (String) params.get("sortField"); // 排序字段
    if(StringUtils.isNoneBlank(sortValue)&& StringUtils.isNoneBlank(sortField)){
        // 2. 创建排序对象
        Sort sort = new Sort("ASC".equalsIgnoreCase(sortValue) ?
                Sort.Direction.ASC : Sort.Direction.DESC, sortField);
        // 3. 设置添加排序
        query.addSort(sort);
    }
...
  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值