本教程仅做个人工作笔记,可能不适用于他人的工作/学习

写在前面

实际生产中,除了考虑产品的性能跟用户体验之外,生产成本也是要考虑的,如果系统是小系统,想对某一个表(200w)做全文检索的话,可以考虑使用mysql自带的full_text索引,没必要使用elasticsearch,毕竟开发相关功能、维护还有服务器的费用,都是相当可观的一笔支出

以下是在mysql中的测试,数据是260w左右

-- 无设置索引 260万条数据查询大概4秒/新增索引之后消耗0.01秒
SELECT * FROM full_text WHERE title = '中国药学杂志'
-- 设置索引后模糊查询还是消耗4秒多,说明百分号前置的情况下索引不生效
SELECT * FROM full_text WHERE title LIKE '%中国药学杂志'
-- 如果将百分号后置,则能使用索引查询,消耗0.01秒
SELECT * FROM full_text WHERE title LIKE '中国药学杂志%'
-- 如果前后都放置%符号,也是不走索引查询的
SELECT * FROM full_text WHERE title LIKE '%中国药学杂志%'
-- 当表中有数据时,新增索引消耗了12秒
ALTER TABLE full_text ADD INDEX index_title (title)

适当的设置索引还是可以很大程度解决查询慢的问题的(前提是充分理解业务,将表设计好)

当然如果很有钱或者是想用ELK全套的,另说

Elasticsearch整合Springboot实现基本的全文检索

前期准备

  • 项目为springboot+maven
  • 正确部署可用elasticsearch服务

添加相关依赖

根据你的elasticsearch版本选择合适的maven依赖,添加到项目中,截至今天2019/12/27,maven上最新的spring-data-elasticsearch的版本是3.2.3.RELEASE,笔者使用es版本是6.6,maven依赖如下

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

创建实体类(Mappings)
Elasticsearch一个核心就是mappings,【Elasticsearch的mappings】
es的java高级客户端可以通过拼接json去创建一个mappings,代码相对繁琐一点,spring-data-elasticsearch提供了一种用注释创建mappings的方案,首先我们创建一个基本的实体类,如

public class Metadata implements Serializable {private static final long serialVersionUID = 1L;private Long recordId;private Integer metadataType;private Long bookRecNo;private String title;
}

稍微做一下改动

import org.springframework.data.elasticsearch.annotations.Field;@Data
@Document(indexName = "your_index_name", type = "metadata")
public class Metadata implements Serializable {private static final long serialVersionUID = 1L;@Id@JsonSerialize(using = ToStringSerializer.class)private Long recordId;@Field(type = FieldType.Integer)private Integer metadataType;@Field(type = FieldType.Long)@JsonSerialize(using = ToStringSerializer.class)private Long bookRecNo;@Field(type = FieldType.Text, analyzer = "ik_smart", fielddata = true)private String title;
}

解释一下

  • @Data:这个是lombok的,与es无关
  • @JsonSerialize(using = ToStringSerializer.class):Long类型数据到js渲染时会有精度缺失问题,这里是让数据在序列化的时候保持精度
  • @Document(indexName = “your_index_name”, type = “metadata”):这是索引的基本信息,索引名,索引类型
  • @Field(type = FieldType.Text, analyzer = “ik_smart”, fielddata = true):
    这个则是属于es的注解,type是es文档中字段的类型,如Long、Text、Keyword等;analyzer是指定分词器;当一个字段需要用来做聚合时,比如排序,则需要将fielddata设置为true,注意,fielddata=true时,会消耗索引内存,所以不需要将每一个的fielddata设置为true,其默认为false;另外需要注意一点,如果一个字段我们需要他做一些类似SQL中group by的操作,我们需要将其的type设置为Keyword,即@Field(type = FieldType.Keyword)

ElasticsearchRepository
创建一个接口继承ElasticsearchRepository用来对索引进行基本的数据操作,代码如下:

import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
@Component
public interface DataRepository extends ElasticsearchRepository<Metadata,Long> {
}

配置文件 yml
spring-data-elasticsearch链接ES服务器的操作不用像java高级客户端那样在代码中实现,而是通过配置文件连接ES的节点,通过实体类的注解以及Repository来指定跟操作对应索引(index、type),以下是springboot项目application.yml关于ES的配置

spring:data:elasticsearch:cluster-name: my-es-servercluster-nodes: 127.0.0.1:9300repositories:enabled: true

以上工作完成后,可以通过代码对索引进行一些基本的操作了

通过代码对索引进行基本操作

建议先去官方文档大致了解一下 Document Api

对索引的操作需要用到org.springframework.data.elasticsearch.core.ElasticsearchTemplate;它可以对索引进行一些基本的操作,也可以对索引中的数据进行操作,然后对数据操作,笔者使用的是上面提到的ElasticsearchRepository

 @AutowiredElasticsearchTemplate template;@Overridepublic void createIndex() {//直接使用带注解的实体类创建索引,会有默认的setting跟mappingsboolean index = template.createIndex(Metadata.class);//使用带注解的实体类跟自定义settings设置创建索引boolean index1 = template.createIndex(Metadata.class, null);//使用带注解的实体类创建索引,会有默认的setting跟mappingsboolean indexName = template.createIndex("indexName");//使用带注解的实体类跟自定义settings设置创建索引boolean indexName1 = template.createIndex("indexName", null);}@Overridepublic void createMappings() {/*** 在创建索引后,如果没有给一个确切的mappings是可以的,但是他的字段会根据你后面给什么数据而创建默认的mappings*/boolean b = template.putMapping(Metadata.class);boolean b1 = template.putMapping("indexName", "typeName", Metadata.class);}@Overridepublic void getMappingsInfo() {//根据实体类获取Map<String, Object> mapping = template.getMapping(Metadata.class);//指定index、type获取Map<String, Object> mapping1 = template.getMapping("indexName", "indexType");}@Overridepublic void getSettingsInfo() {//获取settings信息Map<String, Object> setting = template.getSetting(Metadata.class);Map<String, Object> indexName = template.getSetting("indexName");}@Overridepublic void deleteIndex() {//删除索引信息boolean b = template.deleteIndex(Metadata.class);boolean indexName = template.deleteIndex("IndexName");}

附上一个es索引的信息

{"state": "open","settings": {"index": {"refresh_interval": "1s","number_of_shards": "5","provided_name": "data_collect","creation_date": "1576738381899","store": {"type": "fs"},"number_of_replicas": "1","uuid": "kPp7n73uRYWO5U30BAbIMQ","version": {"created": "6060099"}}},"mappings": {"metadata": {"properties": {"recordId": {"type": "text","fields": {"keyword": {"ignore_above": 256,"type": "keyword"}}},"metadataType": {"type": "integer"},"title": {"fielddata": true,"analyzer": "ik_smart","type": "text"},"bookRecNo": {"type": "long"}}}},"aliases": [],"primary_terms": {"0": 1,"1": 1,"2": 1,"3": 1,"4": 1},"in_sync_allocations": {"0": ["0ACHmsl-SpObHXqLjLRxGw"],"1": ["QZch3H8ESYaPMDIWxBz3kg"],"2": ["KFGEePccSZ-zfKuzXLvb0w"],"3": ["dPTdMlgzQDaE_tUBXCtnrQ"],"4": ["ZYRQp9biQ-2MuDAyVeJboA"]}
}

通过代码对Elasticsearch中的文档进行基本的增删改操作

先看代码

    @AutowiredDataRepository repository;@Overridepublic void addDocument() {//单个添加Metadata metadata = new Metadata();Metadata save = repository.save(metadata);//批量添加ArrayList<Metadata> list = new ArrayList<>();for (int i = 0; i < 10; i++) {Metadata data = new Metadata();list.add(data);i = i + 1;}Iterable<Metadata> dataList = repository.saveAll(list);}@Overridepublic void deleteDocument() {//删除所有repository.deleteAll();//根据id删除repository.deleteById(1L);//传输一个实体类(不为空),查询到实体类之后删除其对应的文档,底层还是根据id删除repository.delete(new Metadata());//批量删除ArrayList<Metadata> list = new ArrayList<>();for (int i = 0; i < 10; i++) {Metadata data = new Metadata();list.add(data);i = i + 1;}repository.deleteAll(list);}

修改的话,只要实体类的id不变,那么直接使用save的方法,修改后es上该文档的version会+1;

实现复杂检索

建议先去官方文档大致了解一下 Query DSL

先看一下基本的代码流程

    @AutowiredDataRepository repository;@Overridepublic Page<Metadata> getFromElastic(Map map) {//检索条件集合,后面需要实现的检索条件都存放在这里BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();//分页PageRequest pageRequest = PageRequest.of(Integer.valueOf(map.get("page").toString()),Integer.valueOf(map.get("limit").toString()));//排序SortBuilder sort = SortBuilders.fieldSort("publishDate").order(SortOrder.DESC);String sortBy = (String) map.get("sortBy");if (!StringUtil.isNullOrEmpty(sortBy)) {sort = SortBuilders.fieldSort(sortBy).unmappedType("Date");String sortRule = (String) map.get("sortRule");if (!StringUtil.isNullOrEmpty(sortRule) && "ASC".equals(sortRule)) {sort.order(SortOrder.ASC);}}//聚合TermsAggregationBuilder aggJobYear = AggregationBuilders.terms("jobYear").field("jobYear").showTermDocCountError(true);//整合构建NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(boolQuery)//检索条件.withSort(sort)//排序条件.withPageable(pageRequest)//分页条件.addAggregation(aggJobYear)//聚合(分面即SQL的group by)条件.build();Page<Metadata> search = repository.search(query);return search;}

检索结果

{"content": [//数据省略],"pageable": {"sort": {"sorted": false,"unsorted": true,"empty": true},"offset": 0,"pageSize": 10,"pageNumber": 0,"paged": true,"unpaged": false},"facets": [{"name": "jobYear","type": "term","terms": [{"term": "2019","count": 551}],"total": 1,"other": 0,"missing": 0}],"aggregations": {"asMap": {"jobYear": {"name": "jobYear","metaData": null,"buckets": [{"docCount": 551,"docCountError": 0,"aggregations": {"asMap": {},"fragment": true},"key": 2019,"keyAsString": "2019","keyAsNumber": 2019,"fragment": true}],"docCountError": 0,"writeableName": "lterms","sumOfOtherDocCounts": 0,"type": "lterms","fragment": true,"mapped": true}},"fragment": true},"scrollId": null,"maxScore": 1,"totalElements": 1135,"totalPages": 114,"number": 0,"size": 10,"sort": {"sorted": false,"unsorted": true,"empty": true},"first": true,"numberOfElements": 10,"last": false,"empty": false
}

大致的检索就如上面那样,关注点放在boolQuery中,其他的基本不变,但是这种检索存在问题,聚合addAggregation的问题,如果用来做分面的字段是Text类型(对应java中的String),检索结果的json序列化会有转化的问题,笔者尝试了很多方法都解决不了,网上相关的解决方案也不多,笔者的做法是将普通的检索跟分面操作分开,然后将两个结果手动封装后再返回给接口调用方;

接下来着重看一下QueryBuilders的各种操作,可以打印一下boolQuery,查看对应的DSL;比如上面的操作对应的DSL语句是:

{"bool" : {"adjust_pure_negative" : true,"boost" : 1.0}
}

ES常用检索

分页、排序不在赘述
实现类似SQL的Like查询
实现对指定字段的精确查询

Elasticsearch整合Springboot实现基本的全文检索相关推荐

  1. elasticsearch整合springBoot

    elasticsearch的安装请参考:https://blog.csdn.net/qq_42410605/article/details/97884456 elasticsearch插件head的安 ...

  2. ElasticSearch整合SpringBoot的API操作

    在我们熟悉了 es 的基本rest 的操作之后,我们将使用SpringBoot进行整合,进一步熟悉Java API的相关操作. 1.创建一个标准的Springboot项目,引入Boot相关依赖之后,还 ...

  3. 关于ElasticSearch整合SpringBoot

    首先导入需要的依赖:这里要注意导入的Jest版本号和你的elasticsearch版本号在同一大版本,我的ES是6.5.4,所以这里我用的6版本的jest docker run -e ES_JAVA_ ...

  4. es springboot 不设置id_es(elasticsearch)整合SpringCloud(SpringBoot)搭建教程详解

    注意:适用于springboot或者springcloud框架 1.首先下载相关文件 2.然后需要去启动相关的启动文件 3.导入相关jar包(如果有相关的依赖包不需要导入)以及配置配置文件,并且写一个 ...

  5. 拥抱 Elasticsearch:给 TiDB 插上全文检索的翅膀

    拥抱 Elasticsearch:给 TiDB 插上全文检索的翅膀 作者介绍:孙晓光,知乎技术平台负责人,与薛宁(@Inke).黄梦龙(@PingCAP).冯博(@知乎)组队参加了 TiDB Hack ...

  6. 好玩的ES--第三篇之过滤查询,整合SpringBoot

    好玩的ES--第三篇之过滤查询,整合SpringBoot 过滤查询 过滤查询 使用 类型 term . terms Filter ranage filter exists filter ids fil ...

  7. springboot集成elasticsearch7实现全文检索及分页

    springboot集成elasticsearch7实现全文检索及分页 elasticsearch系列文章前面已经更新过三篇(https://blog.csdn.net/lsqingfeng/cate ...

  8. 使用Gradle整合SpringBoot+Vue.js-开发调试与打包

    为什么80%的码农都做不了架构师?>>>    非常感谢两位作者: kevinz分享的文章<springboot+gradle+vue+webpack 组合使用> 首席卖 ...

  9. 微信公众号授权步骤详细步骤介绍和整合springboot开发(java版)

    文章有不当之处,欢迎指正,如果喜欢微信阅读,你也可以关注我的微信公众号:好好学java,获取优质学习资源. 一.微信公众号授权步骤 首先到微信公众平台注册账号,可以看到有四种类型(服务号,订阅号,小程 ...

最新文章

  1. 二叉树简介及C++实现
  2. R堆叠柱状图各成分连线画法:突出展示组间物种丰度变化
  3. 解决linux 升级高版本python3.7后yum不能使用的问题
  4. 165. 小猫爬山【爆搜+剪枝】
  5. 挂代理无法访问网页了怎么办_微信H5网页授权
  6. Swoole报错Failed to execute ‘send’ on ‘WebSocket’: Still in CONNECTING state. a
  7. 【HNOI】 lct tree-dp
  8. 监控和审计 Oracle 数据库错误
  9. iOS oc 线程 进程,同步异步,并发串行,来捋捋
  10. 因为重置网络“netsh winsock reset”导致开不了机
  11. 笨办法学python3-笨办法学python3PDF
  12. Java NumberFormat,DecimalFormat保存小数位数
  13. Centos7 [Errno 14] curl#37 - Couldn't open file /mnt/Packages/repodata/repomd.xml
  14. mac 系统服务器地址ping不通,MAC ping IP 地址(ping通/ping不通)
  15. Micropython——关于Pico中I2C和SPI的实际应用示例(OLED屏幕显示)
  16. /usr/bin/ld: cannot find -lxxx的错误
  17. linux命令如何查看dns,详解Linux系统使用dig/nslookup命令查看dns解析的方法
  18. AutoformR8 软件安装说明视频教程
  19. mysql创建数据库utf_MYSQL创建utf-8格式的数据库_MySQL
  20. 我等到花儿也谢 - 张学友 - 真爱

热门文章

  1. 搭建一个apache+php+mysql的网站
  2. xampp修改mysql密码_XAMPP环境下mysql的root用户密码修改方法_MySQL
  3. cocos2dx游戏开发——微信打飞机学习笔记(七)——Enemy的搭建
  4. code函数oracle列子,Oracle内置函数SQLCODE和SQLERRM的使用
  5. Java面试题全集(C)
  6. JAVA多线程信号量Semaphore
  7. 进制转换小技巧之让你重新认识二进制补码(大师,我悟了)!!!
  8. PS——让背景有模糊感(运动感)
  9. 考研积极自救day3
  10. 分享4个嵌入式资深工程师的公众号