Java中Elasticsearch 7.17极简接入 java中elasticsearch 中的Query所有用法
一.前言
Elaticsearch简称为ES, 一个开源的可扩展的分布式全文检索引擎服务器,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。ES使用Java开发并使用Lucene作为其核心来实现索引和搜索的功能,它通过简单的RestfulAPI和JavaAPI来隐藏Lucene的复杂性,从而让全文搜索变得简单。
ES官网:Elasticsearch:官方分布式搜索和分析引擎 | Elastic
1.1场景
分布式的搜索引擎 分布式:Elasticsearch自动将海量数据分散到多台服务器上去存储和检索 搜索:百度、谷歌,站内搜索 全文检索 提供模糊搜索等自动度很高的查询方式,并进行相关性排名,高亮等功能 数据分析引擎(分组聚合) 电商网站,最近一周笔记本电脑这种商品销量排名top10的商家有哪些?新闻网站,最近1个月访问量排名top3的新闻板块是哪些 对海量数据进行近实时的处理 海量数据的处理: 由于是分布式架构,Elasticsearch可以采用大量的服务器去存储和检索数据, 天 然而然就可以实现海量数据的处理 近实时:Elasticsearch可以实现秒级别的数据搜索和分析
1.2概念
索引库[index]-----------------------------------Database 数据库 类型[type]----------------------------------Table 数据表 文档[Document]--------------------------Row 行 字段[Field]-------------------------Column 列 映射[ pping]-------------------表结构1.3详情
索引 | 索引包含一堆相关业务,结构相似的文档数据,比如说建立一个商品product索引库,里面就存放了所有的商品数据。 |
类型 | 类型是索引库中的一个逻辑数据分类,一个类型下的文档,都有相同的字段,类似于数据库中的表。比如商品类型,里面存放了所有的商品文档数据。6.0版本以前每个索引里可以 一个或多个类型,6.0版本以后一个索引只能有1个类型,7.x以后已经移除了这个概念。 |
文档 | 文档是存入索引库最小数据单元,一个文档可以是一条客户数据,一条商品数据,一条订单数据,通常用Json数据结构表示。文档存在索引库下的类型中。 |
字段 | Field是Elasticsearch的最小单位。一个document里面有多个field,每个field就 一个数据字段。 |
映射 | 类型对文档结构的约束叫做映射( pping),用来定义document的每个字段的约束。如:字段的数据类型、是否分词、是否索引、是否存储等特性。类型是模拟mysql中的table概念。表是有结构的,也就是表中每个字段都有约束信息。 |
最新版ES中的 index、document、filed、 pping这样的概念,对比参考MySQL如下:
index | 表 |
document | 行 |
field | 列 |
pping | 表结构 |
1.4数据示例
要实现全文搜索的效果,不可能使用数据库中like操作去进行比对,这种效率太低了。ES设计了一种全新的 想法,来实现全文搜索。具体操作 经过如下:
将被查询的字段的数据全部文本信息进行拆分,分成若干个词
例如“长沙科技公司”就会被拆分成三个词,分别是“长沙”、“科技”、“公司”,此 经过有专业术语叫做分词。分词的策略不同,分出的效果不一样,不同的分词策略称为分词器。
将分词得到的 结局存储起来,对应每条数据的id
例如id为1的数据中名称这一项的值是“长沙科技公司”,那么分词结束后,就会出现“长沙”对应id为1,“科技”对应id为1,“公司”对应id为1。 例如id为2的数据中名称这一项的值是“长沙岳麓区群众“,那么分词结束后,就会出现“长沙”对应id为2,“岳麓区”对应id为2,“群众”对应id为2。 此时就会出现如下对应 结局,按照上述形式可以对所有文档进行分词。需要注意分词的 经过不是仅对一个字段进行,而是对每一个参与查询的字段都执行,最终 结局汇总到一个表格中。
长沙 | 1,2 |
科技 | 1 |
公司 | 1 |
岳麓区 | 2 |
群众 | 2 |
当进行查询时,如果输入“长沙”作为查询条件,可以通过上述表格数据进行比对,得到id值1,2, 接着根据id值就可以得到查询的 结局数据了。
二.对接 经过
2.1版本对应关系(注意:版本一定要一致,不然会出现意向不到的 难题)
版本地址:Versions :: Spring Data Elasticsearch
2.4.x | 4.1.x | 7.9.x |
2.5.x | 4.2.x | 7.12.x |
2.6.x | 4.3.x | 7.15.x |
2.7.x | 4.4.x | 7.17.x |
2.2版本(Spring Boot2.7.18、Spring Data Elasticsearch4.4.18、Elasticsearch7.17.18)
2.3 ven依赖
<!-- 自动化配置 Elasticsearch连接客户端 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency>2.4yaml中参数
spring: # elasticsearch配置 elasticsearch: uris: 127.0.0.1:9200 # Elasticsearch 连接地址 username: elastic # 账号 password: 123456 # 密码 connection-timeout: 10 # 连接超时 时刻(默认1s) socket-timeout: 30 # 数据读取超时 时刻(默认30s)2.5配置文件bean注入
package cn.iocoder.ydtq.framework.elasticsearch.config; import org.elasticsearch.client.RestHighLevelClient; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.data.elasticsearch.client.ClientConfiguration; import org.springframework.data.elasticsearch.client.RestClients; import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration; import javax.annotation.Resource; import java.time.Duration; / | * Java 高 质量别REST客户端是Elasticsearch的默认客户端 * * @author hw */ @AutoConfiguration @EnableConfigurationProperties(EsProperties.class) public class RestClientConfig extends AbstractElasticsearchConfiguration { @Resource private EsProperties esProperties; @Bean @Override public RestHighLevelClient elasticsearchClient() { // 使用构建器来提供集群地址,设置默认值HttpHeaders或启用SSL。 ClientConfiguration clientConfiguration = ClientConfiguration.builder() // 设置es连接地址 // .connectedTo("127.0.0.1:9200") .connectedTo(esProperties.getUris()) // 可以设置多个地址 // .connectedTo("127.0.0.1:9200", "127.0.0.1:9201") // 是否启用ssl // .usingSsl() // 设置连接超时 时刻 .withConnectTimeout(Duration.ofSeconds(esProperties.getConnectionTimeout())) // 设置 .withSocketTimeout(Duration.ofSeconds(esProperties.getSocketTimeout())) // 设置用户名密码 .withBasicAuth(esProperties.getUsername(), esProperties.getPassword()) // 创建连接信息 .build(); // 创建RestHighLevelClient。 return RestClients.create(clientConfiguration).rest(); } } package cn.iocoder.ydtq.framework.elasticsearch.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import javax.validation.constraints.NotEmpty; / | * ES配置 * * @author hw */ @ConfigurationProperties(prefix = "spring.elasticsearch") @Data public class EsProperties { @NotEmpty(message = "ES连接地址不能为空") private String uris; @NotEmpty(message = "账号不能为空") private String username; @NotEmpty(message = "密码不能为空") private String password; @NotEmpty(message = "连接超时 时刻不能为空") private Long connectionTimeout; @NotEmpty(message = "数据读取超时 时刻不能为空") private Long socketTimeout; }2.6IK分词枚举(注意:如需使用IK分词,必须先安装IK分词器)
package cn.iocoder.ydtq.framework.elasticsearch.core. yzer; / | * ES 字段分析器的枚举类(注意先安装IK分词器) * <p> * 关于 IK 分词,文章 https://blog.csdn.net/xsdxs/article/details/72853288 不错。 * 目前项目使用的 ES 版本是 6.7.1 ,可以在 https:// .elastic.co/cn/downloads/past-releases/elasticsearch-6-7-1 下载。 * 如果不知道 如何安装 ES ,可以看 https://blog.csdn.net/chengyuqiang/article/details/78837712 简单。 */ public class FieldAnalyzer { / | * IK 最大化分词 * <p> * 会将文本做最细粒度的拆分 */ public static final String IK_MAX_WORD = "ik_ x_word"; / | * IK 智能分词 * <p> * 会做最粗粒度的拆分 */ public static final String IK_ ART = "ik_ art"; }2.7.注解说明
在MappingElasticsearchConverter使用元数据驱动的对象的映射文件。元数据取自可以注释的实体属性。 提供 下面内容注释: @Document:在类级别应用,以指示该类是映射到数据库的候选对象。最重要的属性是: indexName:用于存储此实体的索引的名称。它可以包含SpEL模板表达式,例如 "log-#{T(java.time.LocalDate).now().toString()}" createIndex:标记是否在存储库引导中创建索引。默认值为true。请参见使用相应的映射自动创建索引 versionType:版本管理的配置。默认值为EXTERNAL。 @Id:在字段级别应用,以标记用于标识目的的字段。 @Transient:默认情况下,存储或检索文档时,所有字段都映射到文档,此注释不包括该字段。 @PersistenceConstructor:标记从数据库实例化对象时要使用的给定构造函数,甚至是受保护的程序包。构造函数参数按名称映射到检索到的Document中的键值。 @Field:在字段级别应用并定义字段的属性,大多数属性映射到各自的Elasticsearch映射定义( 下面内容列表不完整,请查看注释Javadoc以获得完整参考): name:字段名称,它将在Elasticsearch文档中表示,如果未设置,则使用Java字段名称。 type:字段类型,可以是Text, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float, Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type。请参阅Elasticsearch映射类型 for t和日期类型的pattern定义。必须为日期类型定义for t store:标记是否将原始字段值存储在Elasticsearch中,默认值为false。 yzer,searchAnalyzer,nor lizer用于指定自定义分析和正规化。 @GeoPoint:将字段标记为geo_point数据类型。如果字段是GeoPoint类的实例,则可以省略。 @ValueConverter:定义用于转换给定属性的类。与注册的 Spring 不同,Converter这仅转换带注释的属性,而不是给定类型的每个属性。 @Setting:注释定义不同的索引设置。 下面内容参数可用: useServerConfiguration 不发送任何设置参数,因此 Elasticsearch 服务器配置确定它们。 settingPath 是指定义必须在类路径中解析的设置的 JSON 文件 shards要使用的分片数,默认为1 replicas副本数,默认为1 refreshIntervall, 默认为“1s” indexStoreType, 默认为"fs"2.8实体
package cn.iocoder.ydtq.module.system.dal.dataobject.es; import lombok.Data; 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; // 索引名 @Document(indexName = "spu" ) @Data public class ESProductDO implements Serializable { / | * ID 主键 */ @Id private Long id; / | * SPU 名字 */ // @Field( yzer = FieldAnalyzer.IK_MAX_WORD, type = FieldType.Text) private String name; / | * 卖点 */ // @Field( yzer = FieldAnalyzer.IK_MAX_WORD, type = FieldType.Text) private String sellPoint; / | * 描述 */ // @Field( yzer = FieldAnalyzer.IK_MAX_WORD, type = FieldType.Text) private String description; / | * 关键词 */ // @Field( yzer = FieldAnalyzer.IK_MAX_WORD, type = FieldType.Text) private String keywords; / | * 分类编号 */ private Long cid; / | * 分类名 */ // @Field( yzer = FieldAnalyzer.IK_MAX_WORD, type = FieldType.Text) private String categoryName; } package cn.iocoder.ydtq.module.system.controller.admin.es.vo; import cn.iocoder.ydtq.framework.common.pojo.PageParam; import io.swagger.v3.oas.annotations.media.Sche ; import lombok.Data; import lombok.EqualsAndHashCode; @Sche (description = "管理后台 - Es分页 Request VO") @Data @EqualsAndHashCode(callSuper = true) public class ESProductPageReqVO extends PageParam { @Sche (description = "关键词查询参数", example = "1024") private String keyword; @Sche (description = "分类编号", example = "1024") private Long cid; } package cn.iocoder.ydtq.module.system.controller.admin.es.vo; import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; import io.swagger.v3.oas.annotations.media.Sche ; import lombok.Data; @Sche (description = "管理后台 - ES信息 Response VO") @Data @ExcelIgnoreUnannotated public class ESProductRespVO { @Sche (description = "编号", example = "1024") private Long id; @Sche (description = "SPU 名字", example = "1024") private String name; @Sche (description = "卖点", example = "1024") private String sellPoint; @Sche (description = "描述", example = "1024") private String description; @Sche (description = "分类编号", example = "1024") private Long cid; @Sche (description = "分类名", example = "1024") private String categoryName; } package cn.iocoder.ydtq.module.system.controller.admin.es.vo; import io.swagger.v3.oas.annotations.media.Sche ; import lombok.Data; @Sche (description = "管理后台 - ES创建/修改 Request VO") @Data public class ESProductSaveReqVO { @Sche (description = "编号", example = "1024") private Long id; @Sche (description = "SPU 名字", example = "1024") private String name; @Sche (description = "卖点", example = "1024") private String sellPoint; @Sche (description = "描述", example = "1024") private String description; @Sche (description = "关键词", example = "1024") private String keywords; @Sche (description = "分类编号", example = "1024") private Long cid; @Sche (description = "分类名", example = "1024") private String categoryName; }2.9数据操作
package cn.iocoder.ydtq.module.system.controller.admin.es; import cn.iocoder.ydtq.framework.common.pojo.CommonResult; import cn.iocoder.ydtq.framework.common.pojo.PageResult; import cn.iocoder.ydtq.framework.common.util.object.BeanUtils; import cn.iocoder.ydtq.module.system.controller.admin.es.vo.ESProductPageReqVO; import cn.iocoder.ydtq.module.system.controller.admin.es.vo.ESProductRespVO; import cn.iocoder.ydtq.module.system.controller.admin.es.vo.ESProductSaveReqVO; import cn.iocoder.ydtq.module.system.service.es.ESProductService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import org.springframework.validation.annotation.Validated; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.validation.Valid; import static cn.iocoder.ydtq.framework.common.pojo.CommonResult.success; @Tag(name = "管理后台 - ES product") @RestController @RequestMapping("/system/es") @Validated public class ESProductController { @Resource private ESProductService esProductService; @PostMapping("/create") @Operation(sum ry = "创建ES数据") public CommonResult<Long> createEs(@Valid @RequestBody ESProductSaveReqVO createReqVO) { Long postId = esProductService.createEs(createReqVO); return success(postId); } @PutMapping("/update") @Operation(sum ry = "修改ES数据") public CommonResult<Boolean> updateEs(@Valid @RequestBody ESProductSaveReqVO updateReqVO) { esProductService.updateEs(updateReqVO); return success(true); } @DeleteMapping("/delete") @Operation(sum ry = "删除ES数据") public CommonResult<Boolean> deleteEs(@RequestParam("id") Long id) { esProductService.deleteEs(id); return success(true); } @GetMapping(value = "/get") @Operation(sum ry = "获得ES数据信息") @Parameter(name = "id", description = "岗位编号", required = true, example = "1024") public CommonResult<ESProductRespVO> getEs(@RequestParam("id") Long id) { return success(BeanUtils.toBean(esProductService.getEs(id), ESProductRespVO.class)); } @GetMapping("/page") @Operation(sum ry = "获得ES数据分页列表") public CommonResult<PageResult<ESProductRespVO>> getEsPage(@Validated ESProductPageReqVO pageReqVO) { return success(BeanUtils.toBean(esProductService.getEsPage(pageReqVO), ESProductRespVO.class)); } } package cn.iocoder.ydtq.module.system.service.es; import cn.iocoder.ydtq.framework.common.pojo.PageResult; import cn.iocoder.ydtq.module.system.controller.admin.es.vo.ESProductPageReqVO; import cn.iocoder.ydtq.module.system.controller.admin.es.vo.ESProductSaveReqVO; import cn.iocoder.ydtq.module.system.dal.dataobject.es.ESProductDO; import javax.validation.Valid; / | * ES product Service 接口 * * @author hw */ public inte ce ESProductService { Long createEs(@Valid ESProductSaveReqVO createReqVO); Boolean updateEs(@Valid ESProductSaveReqVO updateReqVO); Boolean deleteEs(Long id); ESProductDO getEs(Long id); PageResult<ESProductDO> getEsPage(ESProductPageReqVO pageReqVO); } package cn.iocoder.ydtq.module.system.service.es; import cn.iocoder.ydtq.framework.common.pojo.PageResult; import cn.iocoder.ydtq.framework.common.util.object.BeanUtils; import cn.iocoder.ydtq.module.system.controller.admin.es.vo.ESProductPageReqVO; import cn.iocoder.ydtq.module.system.controller.admin.es.vo.ESProductSaveReqVO; import cn.iocoder.ydtq.module.system.dal.dataobject.es.ESProductDO; import cn.iocoder.ydtq.module.system.dal.repository.ProductRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.data.do in.*; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; / | * ES product Service 实现类 * * @author hw */ @Service @Validated @Slf4j public class ESProductServiceImpl implements ESProductService { @Resource private ProductRepository productRepository; // 复杂查询 @Resource private ElasticsearchRestTemplate elasticsearchRestTemplate; @Override public Long createEs(ESProductSaveReqVO createReqVO) { ESProductDO save = productRepository.save(BeanUtils.toBean(createReqVO, ESProductDO.class)); return save.getId(); } @Override public Boolean updateEs(ESProductSaveReqVO updateReqVO) { ESProductDO save = productRepository.save(BeanUtils.toBean(updateReqVO, ESProductDO.class)); return true; } @Override public Boolean deleteEs(Long id) { productRepository.deleteById(id); return true; } @Override public ESProductDO getEs(Long id) { return productRepository.findById(id).get(); } @Override public PageResult<ESProductDO> getEsPage(ESProductPageReqVO pageReqVO) { PageResult<ESProductDO> pageResult = new PageResult<>(); Pageable pageable = PageRequest.of(pageReqVO.getPageNo(), pageReqVO.getPageSize(), Sort.by(Sort.Direction.DESC, "id")); Page<ESProductDO> page = productRepository.findAll(pageable); pageResult.setList(page.getContent()); pageResult.setTotal(page.getTotalElements()); return pageResult; } } package cn.iocoder.ydtq.module.system.dal.repository; import org.springframework.data.do in.Page; import org.springframework.data.do in.Pageable; import cn.iocoder.ydtq.module.system.dal.dataobject.es.ESProductDO; import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder; import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; import org.springframework.data.do in.PageRequest; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import static org.elasticsearch.index.query.QueryBuilders. tchQuery; @Service public inte ce ProductRepository extends ElasticsearchRepository<ESProductDO, Long> { / | * 搜索查询 * * @param name 商品名称 * @param keywords 商品关键字 * @param page 分页信息 */ // Page<ESProductDO> findByNameOrKeywords(String name, String keywords,Pageable page); // ESProductDO findByName(String name); // // Page<ESProductDO> findByNameLike(String name, Pageable pageable); // // // default Page<ESProductDO> search(Integer cid, String keyword, Pageable pageable) { // NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder(); // // 筛选条件 cid // if (cid != null) { // nativeSearchQueryBuilder.withFilter(QueryBuilders.termQuery("cid", cid)); // } // // 筛选 // if (StringUtils.hasText(keyword)) { // FunctionScoreQueryBuilder.FilterFunctionBuilder[] functions = { // TODO 芋艿,分值随便打的 // new FunctionScoreQueryBuilder.FilterFunctionBuilder( tchQuery("name", keyword), // ScoreFunctionBuilders.weightFactorFunction(10)), // new FunctionScoreQueryBuilder.FilterFunctionBuilder( tchQuery("sellPoint", keyword), // ScoreFunctionBuilders.weightFactorFunction(2)), // new FunctionScoreQueryBuilder.FilterFunctionBuilder( tchQuery("categoryName", keyword), // ScoreFunctionBuilders.weightFactorFunction(3)), //// new FunctionScoreQueryBuilder.FilterFunctionBuilder( tchQuery("description", keyword), //// ScoreFunctionBuilders.weightFactorFunction(2)), // TODO 芋艿,目前这么做,如果商品描述很长,在按照价格降序,会命中超级多的关键字。 // }; // FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(functions) // .scoreMode(FunctionScoreQuery.ScoreMode.SUM) // .setMinScore(2F); // TODO 芋艿,需要考虑下 score // nativeSearchQueryBuilder.withQuery(functionScoreQueryBuilder); // } // // 排序 // if (StringUtils.hasText(keyword)) { // 关键字,使用打分 // nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC)); // } else if (pageable.getSort().isSorted()) { // 有排序,则进行拼接 // pageable.getSort().get().forEach(sortField -> nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField.getProperty()) // .order(sortField.getDirection().isAscending() ? SortOrder.ASC : SortOrder.DESC))); // } else { // 无排序,则按照 ID 倒序 // nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("id").order(SortOrder.DESC)); // } // // 分页 // nativeSearchQueryBuilder.withPageable(PageRequest.of(pageable.getPageNumber(), pageable.getPageSize())); // 避免 // // 执行查询 // NativeSearchQuery build = nativeSearchQueryBuilder.build(); // return search(nativeSearchQueryBuilder.build()); // } }2.10日志输出
logging: file: name: ./log/log.log level: root: info org.springframework.data.elasticsearch.client.WIRE: trace三. 拓展资料
这篇文章小编将简述了Java中ES的使用,以及可能会遇到的坑,大家如果遇到跟多的 难题,欢迎一起沟通交流。