当前位置 : 主页 > 编程语言 > java >

Springboot 中 Elasticsearch 使用

来源:互联网 收集:自由互联 发布时间:2023-02-04
项目中所使用代码已开源 : ​​https://gitee.com/szwei/elasticsearch​​ 项目中使用依赖版本: 依赖 版本 spring-boot 2.3.1.RELEASE elasticsearch 7.9.3-windows-x86_64 kibana 7.8.0-windows-x86_64 一、介绍 回忆时

项目中所使用代码已开源 : ​​https://gitee.com/szwei/elasticsearch​​

项目中使用依赖版本:

依赖

版本

spring-boot

2.3.1.RELEASE

elasticsearch

7.9.3-windows-x86_64

kibana

7.8.0-windows-x86_64

一、介绍

回忆时光

许多年前,一个刚结婚的名叫 Shay Banon 的失业开发者,跟着他的妻子去了伦敦,他的妻子在那里学习厨师。 在寻找一个赚钱的工作的时候,为了给他的妻子做一个食谱搜索引擎,他开始使用 Lucene 的一个早期版本。

直接使用 Lucene 是很难的,因此 Shay 开始做一个抽象层,Java 开发者使用它可以很简单的给他们的程序添加搜索功能。 他发布了他的第一个开源项目 Compass。

后来 Shay 获得了一份工作,主要是高性能,分布式环境下的内存数据网格。这个对于高性能,实时,分布式搜索引擎的需求尤为突出, 他决定重写 Compass,把它变为一个独立的服务并取名 Elasticsearch。

第一个公开版本在2010年2月发布,从此以后,Elasticsearch 已经成为了 Github 上最活跃的项目之一,他拥有超过300名 contributors(目前736名 contributors )。 一家公司已经开始围绕 Elasticsearch 提供商业服务,并开发新的特性,但是,Elasticsearch 将永远开源并对所有人可用。

据说,Shay 的妻子还在等着她的食谱搜索引擎…

不得不说,Elasticsearch 的作者是一个很幽默的人,大概这就是大佬普遍的特性吧,随手一写,就可以开发出足以影响世界的代码。

ES 的特性:

  • 一个分布式的实时文档存储,每个字段 可以被索引与搜索
  • 一个分布式实时分析搜索引擎
  • 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据

二、安装

注意: Elasticsearch 的版本和 springboot 版本 和 Spring Data Elasticsearch 版本需要对应,否则会有各种各样未知错误

Spring Data Release Train

Spring Data Elasticsearch

Elasticsearch

Spring Boot

2020.0.0[​​1​​]

4.1.x[​​1​​]

7.9.3

2.4.x[​​1​​]

Neumann

4.0.x

7.6.2

2.3.x

Moore

3.2.x

6.8.12

2.2.x

Lovelace

3.1.x

6.2.2

2.1.x

Kay[​​2​​]

3.0.x[​​2​​]

5.5.0

2.0.x[​​2​​]

Ingalls[​​2​​]

2.1.x[​​2​​]

2.4.0

1.5.x[​​2​​]

1. 安装 Elasticsearch

下载地址:

​ ​​官网下载地址​​(不推荐,较慢)

​ ​​华为镜像加速​​(推荐,速度 嗖嗖嗖~~)

  • 首先通过官网下载 需要的版本,笔者下载的是Windows 版本的,等待下载完成
  • Windows 版本下载好之后,解压文件夹,双击 bin 目录下的 elasticsearch.bat 启动 ES
  • 等待启动完成后,访问 ​​​http://localhost:9200​​

看到这个返回结果,就是启动完成了。

安装中文分词器,elasticsearch-analysis-ik

下载地址:​​https://github.com/medcl/elasticsearch-analysis-ik/releases​​

下载相对应的版本后,解压,在 elasticsearch 的安装目录,plugins 目录下新建 ik 文件夹,将解压后的文件放到里面,重启 ES

那么现在我们还差一个可视化界面

2.安装 kibana

​ 同样的国外的较慢,直接华为下载 => ​​华为kibana镜像​​

下载后,解压

bin 目录,kibana.bat 启动,kibana 会自动的连接我们刚刚启动的 ES

看到这个页面,说明启动完成

Springboot  中 Elasticsearch  使用_elasticsearch

访问 ​​http://localhost:5601/​​ 即可看到页面

我们选择 左侧的 Dev Tools 进入命令输入页面

Springboot  中 Elasticsearch  使用_spring_02

三、简单使用

索引操作

  • 列出所有索引​​GET /_cat/indices​​
  • 创建新的索引​​PUT /mygoods​​
  • 查询某个索引信息​​GET /mygoods​​
  • 删除某个索引​​DELETE /mygoods​​
  • 文档操作

  • 添加文档
  • PUT /mygoods2/_doc/188{ "id":"188", "name":"添加索引文档", "price":"188", "keywords":"添加索引文档", "subtitle":"添加索引文档", "brandId" : 7, "categoryId" : 5}
  • 删除文档​​DELETE /mygoods/_doc/188​​
  • 简单查询
  • 查询单个
  • ​​GET /mygoods/_doc/188​​

  • 查询所有
  • ​​GET /mygoods/_search​​

  • 表达式查询
  • 查询所有
  • GET /mygoods/_search{ "query" : { "match_all": {} }}
  • 查询条件查询
  • GET /mygoods/_search{ "query" : { "match" : { "name" : "添加索引" } }}

    四、Springboot 中使用

    ### 1. 引入依赖

    查询Springboot 和 Spring Data Elasticsearch 版本,下载对应的 ES , 引入依赖

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

    2. 新建实体类

    package cn.couldme.es.entity;import com.baomidou.mybatisplus.annotation.TableId;import com.baomidou.mybatisplus.annotation.TableName;import lombok.Data;import lombok.EqualsAndHashCode;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;/** * 商品表(Goods)表实体类 * * @Author szwei * @Date 2020-12-06 21:45:37 */@Data@TableName@EqualsAndHashCode(callSuper = false)@Document(indexName = "mygoods",type = "_doc", shards = 1,replicas = 0)public class Goods implements Serializable { private static final long serialVersionUID = -1; /** * 自增 => @TableId(type = IdType.AUTO) */ @TableId private Long id; /*名称*/ @Field(analyzer = "ik_max_word",type = FieldType.Text) private String name; /*价格*/ private Double price; /*关键词*/ @Field(analyzer = "ik_max_word",type = FieldType.Text) private String keywords; /*标题*/ @Field(analyzer = "ik_max_word",type = FieldType.Text) private String subtitle; /*品牌商*/ @Field(type=FieldType.Keyword) private Long brandId; /*分类名称*/ @Field(type=FieldType.Keyword) private Long categoryId;}

    3. 继承 ElasticsearchRepository

    package cn.couldme.es.repository;import cn.couldme.es.entity.Goods;import org.springframework.data.domain.Page;import org.springframework.data.domain.Pageable;import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;/** * 搜索商品ES操作类 * Created by macro on 2018/6/19. */public interface EsGoodsRepository extends ElasticsearchRepository<Goods, Long> { /** * 搜索查询 * * @param name 商品名称 * @param keywords 商品关键字 * @param page 分页信息 */ Page<Goods> findByNameOrKeywords(String name, String keywords, Pageable page);}

    4. EsGoodstService

    package cn.couldme.es.service;import cn.couldme.es.entity.Goods;import org.springframework.data.domain.Page;import java.util.List;/** * 搜索商品管理Service * Created by macro on 2018/6/19. */public interface EsGoodstService { /** * 从数据库中导入所有商品到ES */ int importAll(); /** * 根据id删除商品 */ void delete(Long id); /** * 根据id创建商品 */ Goods create(Long id); /** * 批量添加商品 */ void batchCreate(List<Long> ids); /** * 批量删除商品 */ void batchDelete(List<Long> ids); /** * 根据关键字搜索名称或者副标题 */ Page<Goods> search(String keyword, Integer pageNum, Integer pageSize); /** * 根据关键字搜索名称或者副标题复合查询 */ Page<Goods> search(String keyword, Long brandId, Long categoryId, Integer pageNum, Integer pageSize, Integer sort); /** * 根据商品id推荐相关商品 */ Page<Goods> recommend(Long id, Integer pageNum, Integer pageSize);}

    5. EsGoodstServiceImpl

    @Slf4j@Servicepublic class EsGoodstServiceImpl implements EsGoodstService { @Autowired private GoodsMapper goodsMapper; @Autowired private EsGoodsRepository productRepository; @Autowired private ElasticsearchRestTemplate elasticsearchRestTemplate; @Override public int importAll() { List<Goods> GoodsList = goodsMapper.selectList(null); Iterable<Goods> GoodsIterable = productRepository.saveAll(GoodsList); Iterator<Goods> iterator = GoodsIterable.iterator(); int result = 0; while (iterator.hasNext()) { result++; iterator.next(); } return result; } @Override public void delete(Long id) { productRepository.deleteById(id); } @Override public Goods create(Long id) { Goods result = null; Goods goods = goodsMapper.selectById(id); result = productRepository.save(goods); return result; } @Override public void batchCreate(List<Long> ids) { if (!CollectionUtils.isEmpty(ids)) { List<Goods> spxxList = goodsMapper.selectBatchIds(ids); productRepository.saveAll(spxxList); } } @Override public void batchDelete(List<Long> ids) { if (!CollectionUtils.isEmpty(ids)) { List<Goods> GoodsList = new ArrayList<>(); for (Long id : ids) { Goods goods = new Goods(); goods.setId(id); GoodsList.add(goods); } productRepository.deleteAll(GoodsList); } } @Override public Page<Goods> search(String keyword, Integer pageNum, Integer pageSize) { Pageable pageable = PageRequest.of(pageNum, pageSize); return productRepository.findByNameOrKeywords(keyword, keyword, pageable); } @Override public Page<Goods> search(String keyword, Long brandId, Long categoryId, Integer pageNum, Integer pageSize, Integer sort) { Pageable pageable = PageRequest.of(pageNum, pageSize); NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder(); //分页 nativeSearchQueryBuilder.withPageable(pageable); nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort()); //过滤 if (brandId != null || categoryId != null) { BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); if (brandId != null) { boolQueryBuilder.must(QueryBuilders.termQuery("brandId", brandId)); } if (categoryId != null) { boolQueryBuilder.must(QueryBuilders.termQuery("categoryId", categoryId)); } nativeSearchQueryBuilder.withFilter(boolQueryBuilder); } //搜索 if (StringUtils.isEmpty(keyword)) { nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery()); } else { List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>(); filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("name", keyword), ScoreFunctionBuilders.weightFactorFunction(10))); filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("subTitle", keyword), ScoreFunctionBuilders.weightFactorFunction(5))); filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("keywords", keyword), ScoreFunctionBuilders.weightFactorFunction(2))); FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()]; filterFunctionBuilders.toArray(builders); MultiMatchQueryBuilder matchQuery = QueryBuilders.multiMatchQuery(keyword, "name", "subTitle", "keywords"); FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(matchQuery,builders) .scoreMode(FunctionScoreQuery.ScoreMode.SUM) .setMinScore(2); nativeSearchQueryBuilder.withQuery(functionScoreQueryBuilder); } //排序 if(sort==1){ //按新品从新到旧 nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("id").order(SortOrder.DESC)); }else if(sort==2){ //按销量从高到低 nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("sale").order(SortOrder.DESC)); }else if(sort==3){ //按价格从低到高 nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC)); }else if(sort==4){ //按价格从高到低 nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC)); }else{ //按相关度 nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC)); } nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC)); NativeSearchQuery searchQuery = nativeSearchQueryBuilder.build(); log.info("DSL:{}", searchQuery.getQuery().toString()); SearchHits<Goods> searchHits = elasticsearchRestTemplate.search(searchQuery, Goods.class); if(searchHits.getTotalHits()<=0){ return new PageImpl<>(new ArrayList<>(),pageable,0); } List<Goods> searchProductList = searchHits.stream().map(SearchHit::getContent).collect(Collectors.toList()); return new PageImpl<>(searchProductList,pageable,searchHits.getTotalHits()); } @Override public Page<Goods> recommend(Long id, Integer pageNum, Integer pageSize) { Pageable pageable = PageRequest.of(pageNum, pageSize); Goods goods = goodsMapper.selectById(id); if (Objects.nonNull(goods)) { String keyword = goods.getName(); Long brandId = goods.getBrandId(); Long categoryId = goods.getCategoryId(); //根据商品标题、品牌、分类进行搜索 List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>(); filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("name", keyword), ScoreFunctionBuilders.weightFactorFunction(8))); filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("subTitle", keyword), ScoreFunctionBuilders.weightFactorFunction(2))); filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("keywords", keyword), ScoreFunctionBuilders.weightFactorFunction(2))); filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("brandId", brandId), ScoreFunctionBuilders.weightFactorFunction(5))); filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("productCategoryId", categoryId), ScoreFunctionBuilders.weightFactorFunction(3))); FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()]; filterFunctionBuilders.toArray(builders); //设置查询条件 QueryBuilder queryBuilder = QueryBuilders.boolQuery() .must(QueryBuilders.multiMatchQuery(keyword,"name","subTitle","keywords"));// .should(QueryBuilders.matchQuery("brandId", brandId))// .should(QueryBuilders.matchQuery("categoryId", categoryId)); FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(queryBuilder, builders) .scoreMode(FunctionScoreQuery.ScoreMode.SUM) .setMinScore(2); //用于过滤掉相同的商品 BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder(); boolQueryBuilder.mustNot(QueryBuilders.termQuery("id",id)); //构建查询条件 NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder(); builder.withQuery(functionScoreQueryBuilder); builder.withFilter(boolQueryBuilder); builder.withPageable(pageable); NativeSearchQuery searchQuery = builder.build(); log.info("DSL:{}", searchQuery.getQuery().toString()); SearchHits<Goods> searchHits = elasticsearchRestTemplate.search(searchQuery, Goods.class); if(searchHits.getTotalHits()<=0){ return new PageImpl<>(new ArrayList<>(),pageable,0); } List<Goods> searchProductList = searchHits.stream().map(SearchHit::getContent).collect(Collectors.toList()); return new PageImpl<>(searchProductList,pageable,searchHits.getTotalHits()); } return new PageImpl<>(new ArrayList<>()); }}

    6. 事件通知类

    添加商品事件

    package cn.couldme.core.event;import lombok.Getter;import org.springframework.context.ApplicationEvent;import java.util.List;/** * @Author: szwei * @Date: 2020-12-06 23:03 **/@Getterpublic class GoodsAddEvent extends ApplicationEvent { private List<Long> ids; public GoodsAddEvent(Object source, List<Long> ids) { super(source); this.ids = ids; }}

    删除商品事件

    package cn.couldme.core.event;import lombok.Getter;import org.springframework.context.ApplicationEvent;import java.util.List;/** * @Author: szwei * @Date: 2020-12-06 23:03 **/@Getterpublic class GoodsDelEvent extends ApplicationEvent { private List<Long> ids; public GoodsDelEvent(Object source, List<Long> ids) { super(source); this.ids = ids; }}

    7. 监听器

    package cn.couldme.es.eventListener;import cn.couldme.core.event.GoodsAddEvent;import cn.couldme.core.event.GoodsDelEvent;import cn.couldme.es.service.EsGoodstService;import lombok.AllArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.context.event.EventListener;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Component;import java.util.List;/** * @Author: szwei * @Date: 2020-12-06 23:22 **/@Slf4j@Component@AllArgsConstructorpublic class GoodsListener { private final EsGoodstService esGoodstService; @EventListener @Async public void goodsAddEvent(GoodsAddEvent goodsAddEvent){ List<Long> ids = goodsAddEvent.getIds(); log.debug("往es添加商品: ids => {}",ids); esGoodstService.batchCreate(ids); } @EventListener @Async public void goodsDelEvent(GoodsDelEvent goodsDelEvent){ List<Long> ids = goodsDelEvent.getIds(); log.debug("往es删除商品: ids => {}",ids); esGoodstService.batchDelete(ids); }}

    8. 配置异步

    在启动类添加 ​​@EnableAsync​​注解

    9. 效果图

    Springboot  中 Elasticsearch  使用_spring_03

    五、注意事项

    1. 启动项目报错

    Springboot  中 Elasticsearch  使用_elasticsearch_04

    问题出现原因:

    elasticsearch和Redis都需要Netty作为NIO框架,在Redis初始化时已经对Netty进行了初始化处理器数量,当ES再次尝试初始化Netty处理器数量时,Netty就会对此进行保护措施,抛出异常

    解决方案:

    在启动类上添加:

    ​​ System.setProperty("es.set.netty.runtime.available.processors", "false");​​

    不让es 的 netty 再次去设置

    2. 查询出来的数据没有按照匹配度排序

    在导入数据到 es 的时候需要指定 该字段的使用的分词器

    例如:

    关键词(不会进行分词)​​ @Field(type=FieldType.Keyword)​​

    最大粒度分词​​@Field(analyzer = "ik_max_word",type = FieldType.Text)​​

    上一篇:Java并发JUC——Future和Callable
    下一篇:没有了
    网友评论