ElasticSearch学习总结

简介

  • Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTfulweb接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。
  • 我们建立一个网站或应用程序,并要添加搜索功能,但是想要完成搜索工作的创建是非常困难的。我们希望搜索解
    决方案要运行速度快,我们希望能有一个零配置和一个完全免费的搜索模式,我们希望能够简单地使用JSON通过
    HTTP来索引数据,我们希望我们的搜索服务器始终可用,我们希望能够从一台开始并扩展到数百台,我们要实时
    搜索,我们要简单的多用户,我们希望建立一个云的解决方案。因此我们利用Elasticsearch来解决所有这些问题及
    可能出现的更多其它问题。
  • 安装ElasticSearch
    • ElasticSearch是基于Java编写的解压即用的软件,只需要有Java的运行环境即可,把压缩包解压后,进入到bin目录运行elasticsearch.bat,出现界面,表示成功启动服务器,浏览器输入:http://localhost:9200,看到浏览器输出服务器的信息,表示安装成功
      • 注:注意:程序启动后有两个端口9200和9300,9200端口用于HTTP协议,基于RESTFul来使用,9300端口用于TCP协议,基于jar包来使用
    • 使用安装目录/bin/elasticsearch-service.bat程序可以把Elasticsearch安装后服务列表中,以后我们可以在服务列表来启动该程序,也可以设置成开机启动模式,设置后台启动需要手动配置Java虚拟机的路径,使用命令elasticsearch-service.bat manager来配置
  • 安装可视化界面head插件
    • es head插件,github上面下载
    // https://github.com/mobz/elasticsearch-head
    npm install
    npm run start #启动插件:localhost:9100
    
    • 解决跨域问题:修改elasticsearch.yml文件
    http.cors.enabled: true
    http.cors.allow-origin: "*"
    
  • 安装kibana
    • 是解压即用的工具,用于管理和监控Elasticsearch的运作,同时内部包含了客户端工具,支持RESTFul操作Elasticsearch。解压后运行bin/kibana.bat,看到启动成功的端口号即可以使用浏览器来使用了
    • 默认端口 localhost:5601
  • 安装IK分词器插件
    • 用于对中文分词
    • 两个分词算法:ik_smart(最少切分),ik_max_word(最细粒度划分)
    • 下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases
    • 自定义分词
      • 是找到IK插件中的config/main.dic文件,往里面添加新的词汇,然后重启服务器即可

ES核心概念

  • es是面向文档的,一切都是JSON
  • 对比
关系型数据库Elasticsearch
数据库database索引 index(数据库)
表tablestypes
行rowsdocuments (文档)
字段columnsfields
  • 物理设计:在后台把每个索引划分为多个分片,每片可以再集群中的不同服务器间迁移
  • 逻辑设计:
    • 文档:索引和搜索数据的最小单位是文档
      • 自我包含:key:value
      • 层次型:一个文档中包含文档(json对象)
    • 类型:文档的逻辑容器
    • 索引:数据库
  • 倒排索引:es使用倒排索引的结构,采用Lucene倒排索引作为底层。用于快速全文检索

主要组件

  • 索引
    • ES将数据存储于一个或多个索引中,索引是具有类似特性的文档的集合。类比传统的关系型数据库领域来说,索引相当于SQL中的一个数据库,或者一个数据存储方案(schema)。索引由其名称(必须为全小写字符)进行标识,并通过引用此名称完成文档的创建、搜索、更新及删除操作。一个ES集群中可以按需创建任意数目的索引。
  • 类型
    • 类型是索引内部的逻辑分区(category/partition),然而其意义完全取决于用户需求。因此,一个索引内部可定义一个或多个类型(type)。一般来说,类型就是为那些拥有相同的域的文档做的预定义。例如,在索引中,可以定义一个用于存储用户数据的类型,一个存储日志数据的类型,以及一个存储评论数据的类型。类比传统的关系型数据库领域来说,类型相当于表。
  • 映射
    • Mapping,就是对索引库中索引的字段名称及其数据类型进行定义,类似于mysql中的表结构信息。不过es的mapping比数据库灵活很多,它可以动态识别字段。一般不需要指定mapping都可以,因为es会自动根据数据格式识别它的类型,如果你需要对某些字段添加特殊属性(如:定义使用其它分词器、是否分词、是否存储等),就必须手动添加mapping。
    • 需要注意的是映射是不可修改的,一旦确定就不允许改动,在使用自动识别功能时,会以第一个存入的文档为参考来建立映射,后面存入的文档也必须符合该映射才能存入
  • 文档
    • 文档是Lucene索引和搜索的原子单位,它是包含了一个或多个域的容器,基于JSON格式进行表示。文档由一个或多个域组成,每个域拥有一个名字及一个或多个值,有多个值的域通常称为多值域。每个文档可以存储不同的域集,但同一类型下的文档至应该有某种程度上的相似之处。

分片和副本

  • ES的分片(shard)机制可将一个索引内部的数据分布地存储于多个节点,它通过将一个索引切分为多个底层物理的Lucene索引完成索引数据的分割存储功能,这每一个物理的Lucene索引称为一个分片(shard)。每个分片其内部都是一个全功能且独立的索引,因此可由集群中的任何主机存储。创建索引时,用户可指定其分片的数量,默认数量为5个。
  • Shard有两种类型:primary和replica,即主shard及副本shard。Primary shard用于文档存储,每个新的索引会自动创建5个Primary shard,当然此数量可在索引创建之前通过配置自行定义,不过,一旦创建完成,其Primaryshard的数量将不可更改。Replica shard是Primary Shard的副本,用于冗余数据及提高搜索性能。每个Primaryshard默认配置了一个Replica shard,但也可以配置多个,且其数量可动态更改。ES会根据需要自动增加或减少这些Replica shard的数量。

Rest风格

  • RESTful是一种架构的规范与约束、原则,符合这种规范的架构就是RESTful架构。
methodurl地址描述
PUTlocalhost:9100/索引名称/类型名称/文档id创建文档(指定id)
POSTlocalhost:9100/索引名称/类型名称创建文档(随机id)
POSTlocalhost:9100/索引名称/文档类型/文档id/_update修改文档
DELETElocalhost:9100/索引名称/文档类型/文档id删除文档
GETlocalhost:9100/索引名称/文档类型/文档id查询文档通过文档id
POSTlocalhost:9100/索引名称/文档类型/_search查询所有文档
  • 数据类型
    • 字符串 text, keyword
    • 数据类型 long, integer,short,byte,double,float,half_float,scaled_float
    • 日期 date
    • 布尔 boolean
    • 二进制 binary

elasticsearch.yml配置文件详解

配置Elasticsearch的集群名称,默认是elasticsearch,Elasticsearch会自动发现在同一网段下的Elasticsearch 节点,如果在同一网段下有多个集群,就可以用这个属性来区分不同的集群。
cluster.name: elasticsearch
 
节点名,默认随机指定一个name列表中名字,不能重复。
 
node.name: "node1"
 
指定该节点是否有资格被选举成为node,默认是true,es是默认集群中的第一台机器为master,如果这台机挂了就会重新选举master。
 
node.master: true
 
指定该节点是否存储索引数据,默认为true。
 
node.data: true
 
设置默认索引分片个数,默认为5片。
 
index.number_of_shards: 5
 
设置默认索引副本个数,默认为1个副本。
 
index.number_of_replicas: 1
 
设置配置文件的存储路径,默认是es根目录下的config文件夹。
 
path.conf: /path/to/conf
 
设置索引数据的存储路径,默认是es根目录下的data文件夹
 
path.data: /path/to/data
 
可以设置多个存储路径,用逗号(半角)隔开,如下面这种配置方式:
 
path.data: /path/to/data1,/path/to/data2
 
设置临时文件的存储路径,默认是es根目录下的work文件夹。
 
path.work: /path/to/work
 
设置日志文件的存储路径,默认是es根目录下的logs文件夹
 
path.logs: /path/to/logs
 
设置插件的存放路径,默认是es根目录下的plugins文件夹
 
path.plugins: /path/to/plugins
 
设置为true来锁住内存。因为当jvm开始swapping时es的效率会降低,所以要保证它不swap,可以把ES_MIN_MEM和ES_MAX_MEM两个环境变量设置成同一个值,并且保证机器有足够的内存分配给es。同时也要允许elasticsearch的进程可以锁住内存,linux下可以通过`ulimit -l unlimited`命令。
 
bootstrap.mlockall: true
 
设置绑定的ip地址,可以是ipv4或ipv6的,默认为0.0.0.0。
 
network.bind_host: 192.168.0.1
 
设置其它节点和该节点交互的ip地址,如果不设置它会自动判断,值必须是个真实的ip地址。
 
network.publish_host: 192.168.0.1
 
这个参数是用来同时设置bind_host和publish_host上面两个参数。
 
network.host: 192.168.0.1
 
设置节点间交互的tcp端口,默认是9300,(集群的时候,注意端口区分)。
 
transport.tcp.port: 9300
 
设置是否压缩tcp传输时的数据,默认为false,不压缩。
 
transport.tcp.compress: true
 
设置对外服务的http端口,默认为9200(集群的时候,同台机器,注意端口区分)。
 
http.port: 9200
 
设置内容的最大容量,默认100mb
 
http.max_content_length: 100mb
 
是否使用http协议对外提供服务,默认为true,开启。
 
http.enabled: false
 
gateway的类型,默认为local即为本地文件系统,可以设置为本地文件系统,分布式文件系统,hadoop的HDFS,和amazon的s3服务器。
 
gateway.type: local
 
设置集群中N个节点启动时进行数据恢复,默认为1。
 
gateway.recover_after_nodes: 1
 
设置初始化数据恢复进程的超时时间,默认是5分钟。
 
gateway.recover_after_time: 5m
 
设置这个集群中节点的数量,默认为2,一旦这N个节点启动,就会立即进行数据恢复。
 
gateway.expected_nodes: 2
 
初始化数据恢复时,并发恢复线程的个数,默认为4。
 
cluster.routing.allocation.node_initial_primaries_recoveries: 4
 
添加删除节点或负载均衡时并发恢复线程的个数,默认为4。
 
cluster.routing.allocation.node_concurrent_recoveries: 2
 
设置数据恢复时限制的带宽,如入100mb,默认为0,即无限制。
 
indices.recovery.max_size_per_sec: 0
 
设置这个参数来限制从其它分片恢复数据时最大同时打开并发流的个数,默认为5。
 
indices.recovery.concurrent_streams: 5
 
设置这个参数来保证集群中的节点可以知道其它N个有master资格的节点。默认为1,对于大的集群来说,可以设置大一点的值(2-4)
 
discovery.zen.minimum_master_nodes: 1
 
设置集群中自动发现其它节点时ping连接超时时间,默认为3秒,对于比较差的网络环境可以高点的值来防止自动发现时出错。
 
discovery.zen.ping.timeout: 3s
 
设置是否打开多播发现节点,默认是true。
 
discovery.zen.ping.multicast.enabled: false
 
设置集群中master节点的初始列表,可以通过这些节点来自动发现新加入集群的节点。
 
discovery.zen.ping.unicast.hosts: ["host1", "host2:port", "host3[portX-portY]"]

基本操作

建立索引

语法:PUT /索引名 
在没有特殊设置的情况下,默认有5个分片,1个备份,也可以通过请求参数的方式来指定 
参数格式: 
{ 
	"settings": { 
		"number_of_shards": 5, //设置5个片区 
		"number_of_replicas": 1 //设置1个备份 
	} 
}

删除索引

语法:DELETE /索引名

查看索引

// 查看集群健康信息
语法:GET /_cat/health?v
// 查看节点信息
语法:GET /_cat/nodes?v
// 查看es集群所有索引及数据大小
语法:GET /_cat/indices?v
// 查看单个索引库
语法:GET /索引名?pretty=true

建立索引和映射

语法:PUT /索引名
{
	"mappings": { //描述索引
		类型名: {
			"properties": { //描述字段
				字段名: {
					"type": 字段类型,
					"analyzer": 分词器类型,
					"search_analyzer": 分词器类型,
					"index": "not_analyzed",
					"ignore_above": 20,// 忽略所有长度超过 20 的字符串,对超过的 analyzer 不会进行处理,对 not_analyzed 字段有用
					"fields"{
						字段名: { //附属多字段:用于通过不同的方法索引相同的字段。例如,一个字符串字段可以映射为text字段(被拆分)用于全文本搜索,也可以映射为keyword字段(不被拆分)用于排序或聚合。
							"type": 字段类型
							...
						}
						...
					}
				}
			}
		}
	}
}
// 注意:text和keyword都是字符串类型,但是只有text类型的数据才能分词,字段的配置一旦确定就不能更改映射的配置项有很多,我们可以根据需要只配置用得上的属性

查询映射

GET /索引名/_mapping

新增和修改文档

POST/PUT /索引名/类型名/文档ID
{
	field1: value1, //字段列
	field2: value2,
	...
}
// 注意:当索引/类型/映射不存在时,会使用默认设置自动添加
// ES中的数据一般是从别的数据库导入的,所以文档的ID会沿用原数据库中的ID
// 索引库中没有该ID对应的文档时则新增,拥有该ID对应的文档时则替换
// 更新数据,推荐使用POST _update,例POST psz/user/1/_update
  • 文档内置字段
    • _index:所属索引
    • _type:所属类型
    • _id:文档ID
    • _version:乐观锁版本号
    • _source:数据内容

查询文档

// 根据id查询单个文档
GET /索引名/类型名/文档ID
// 查询所有文档
GET /索引名/类型名/_search 
  • 查询所有结果中包含以下字段
    • took:耗时
    • _shards.total:分片总数
    • hits.total:查询到的数量
    • hits.max_score:最大匹配度
    • hits.hits:查询到的结果
    • hits.hits._score:匹配度

删除文档

// 删除单个
DELETE /索引名/类型名/文档ID
//删除多个
DELETE /索引名/类型名/_delete_by_query
// 这里的删除并且不是真正意义上的删除,仅仅是清空文档内容而已,并且标记该文档的状态为删除

高级查询

  • Elasticsearch基于JSON提供完整的查询DSL(Domain Specific Language:领域特定语言)来定义查询

基本查询

GET /索引名/类型名/_search
  • 一般都是需要配合查询参数来使用的,配合不同的参数有不同的查询效果
  • 参数配置项可以参考博客:https://www.jianshu.com/p/6333940621ec

精确检索查询

  • value值不会被分词器拆分,按照倒排索引匹配,精确一个条件
GET /索引名/类型名/_search
{
	"query": {
		"term": {
			"字段名": "字段值" //或"字段":{"value": "字段值"}
		}
	}
}
  • 精确查询多个值,相当于in操作
GET /索引名/类型名/_search
{
	"query": {
		"terms": {
			"字段名":["字段值1","字段值2"...]
		}
	}
}

全文检索查询

  • value值会被分词器拆分,然后去倒排索引中匹配,即先分词后查询
GET /索引名/类型名/_search
{
	"query": {
		"match": {
			"字段名": "字段值"
		}
	}
}

范围检索查询

  • value值是一个对象,如{ “range”: {field: {比较规则: value, …}} } 比较规则有gt / gte / lt / lte 等
  • 注意:term和match都能用在数值和字符上,range用在数值上
GET /索引名/类型名/_search
{
	"query": {
		"range": {
			"字段名": {
				"比较转义符":"值",
				...
			}
		}
	}
}

关键字查询

  • 在多个字段间做检索,只要其中一个字段满足条件就能查询出来,多用在字段上
GET /索引名/类型名/_search
{
	"query": {
		"multi_match": {
			"query": "字段值",
			"fields": [字段名1, 字段名2, ...]
		}
	}
}

逻辑查询

  • 逻辑规则:must / should / must_not,相当于and / or / not
GET /索引名/类型名/_search
{
	"query": {
		"bool": {
			"逻辑规则": [
				{
					"检索方式": {
						"字段名": "字段值"
					}
				},
				...
			],
			...
		}
	}
}

过滤查询

  • 和检索查询能做一样的效果,区别在于过滤查询不评分,结果能缓存,检索查询要评分,结果不缓存
  • 一般是不会直接使用过滤查询,都是在检索了一定数据的基础上再使用
  • 更多请参考:https://blog.csdn.net/laoyang360/article/details/80468757
GET /索引名/类型名/_search
{
	"query": {
		"bool": {
			"filter": [
				{
					"检索方式": {
						"字段名": "字段值"
					}
				},
				...
			]
		}
	}
}

高亮显示

  • 需要在fields中配置哪些字段中检索到该内容需要高亮显示,必须配合检索(term / match)一起使用
GET /索引名/类型名/_search
{
	"query": { ... },
	"highlight": {
		"fields": {
			"字段名": {},
			...
		},
		"pre_tags": 开始标签,
		"post_tags" 结束标签
	}
}

排序

GET /索引名/类型名/_search
{
	"sort": [
		{
			"字段名":{ //或"字段名":"排序规则"
				"order": "desc" //排序规则
			}
		},
		...
	]
}
// 排序规则:asc表示升序,desc:表示降序,没有配置排序的情况下,默认按照评分降序排列

分页

GET /索引名/类型名/_search
{
	"from": start, //起始索引
	"size": pageSize //长度
}

分组

  • 分组运算:avg / sum / min / max / value_count / stats(执行以上所有功能的)
  • 显示结果中buckets即桶(Buckets):相当于 group by 把若干个数据放入到不同的桶中
  • 指标(Metrics):相当于mysql中的组函数 avg,sum ,max,min等
GET /索引名/类型名/_search
{
	"size": 0,
	"aggs": {
		自定义分组字段: {
			"terms": { //定义多个桶
				"field": 分组字段,
				"order": {自定义统计字段:排序规则},
				"size": 10 //默认显示10组
			},
			"aggs": { //分组后的统计查询,相当于MySQL分组函数查询
				自定义统计字段: {
					分组运算: {
						"field": 统计字段
					}
				}
			}
		}
	}
}
// 注意:这里是size=0其目的是为了不要显示hit内容,专注点放在观察分组上

批处理

  • 当需要集中的批量处理文档时,如果依然使用传统的操作单个API的方式,将会浪费大量网络资源,Elasticsearch
    为了提高操作的性能,专门提供了批处理的API
  • mget批量查询
GET /索引名/类型/_mget
{
	"docs": [
		{"_id": 文档ID},
		...
	]
}
  • bulk批量增删改
POST /索引名/类型/_bulk
{
	{动作:{"_id": 文档ID}}
	{...}
	{动作:{"_id": 文档ID}}
	{...}
}
// 动作:create / update / delete,其中delete只有1行JSON,其他操作都是有2行JSON,并且JSON不能格式化,如果是update动作,它的数据需要加个key为doc
// 例:
// {"update": {"_id": xx}}
// {"doc": {"xx":xx, "xx":xx}}

Spring Data Elasticsearch

  • maven依赖
<dependency> 
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
  • 编写domain实体类
// @Document:配置操作哪个索引下的哪个类型
// @Id:标记文档ID字段
// @Field:配置映射信息,如:分词器
@Getter@Setter@ToString
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "test1",type = "user")
public class User implements Serializable {
    @Id
    private String id;
    @Field(name = "uid",type = FieldType.Integer)
    private Integer uid;
    @Field(name = "uname",type = FieldType.Text,analyzer = "ik_smart")
    private String uname;
    @Field(name = "tel",type = FieldType.Keyword)
    private String  tel;
    @Field(name = "city",type = FieldType.Text)
    private String city;
    @Field(name = "age",type = FieldType.Long)
    private Long age;
    @Field(name = "address",type = FieldType.Text)
    private String address;
}
  • application.properties配置文件
# 配置集群名称,名称写错会连不上服务器,默认elasticsearch
spring.data.elasticsearch.cluster-name=elasticsearch
# 配置集群节点
spring.data.elasticsearch.cluster-nodes=localhost:9300

组件介绍

  • ElasticsearchRepository类
    • 该接口是框架封装的用于操作Elastsearch的高级接口,只要我们自己的写个接口去继承该接口就能直接对Elasticsearch进行CRUD操作
// 泛型1:domain的类型
// 泛型2:文档主键类型
// 该接口直接该给Spring,底层会使用JDK代理的方式创建对象,交给容器管理
@Repository
public interface ProductESRepository extends ElasticsearchRepository<Product, String> {
// 符合Spring Data规范的高级查询方法
}
  • ElasticsearchTemplate类:框架封装的用于便捷操作Elasticsearch的模板类
    • 该模板类,封装了便捷操作Elasticsearch的模板方法,包括 索引 / 映射 / CRUD 等底层操作和高级操作,该对象用起来会略微复杂些,尤其是对于查询,还需要把查询到的结果自己封装对象
    • ElasticsearchTemplate和ElasticsearchRepository是分工合作的,ElasticsearchRepository已经能完成绝大部分的功能,如果遇到复杂的查询则要使用ElasticsearchTemplate,如多字段分组、高亮显示等
//该对象已经由SpringBoot完成自动配置,直接注入即可
@Autowired
private ElasticsearchTemplate template;
  • NativeSearchQueryBuilder类:用于生成查询条件的构建器,需要去封装各种查询条件
  • QueryBuilder:该接口表示一个查询条件,其对象可以通过QueryBuilders工具类中的方法快速生成各种条件
    • boolQuery():生成bool条件,相当于 “bool”: { }
    • matchQuery():生成match条件,相当于 “match”: { }
    • rangeQuery():生成range条件,相当于 “range”: { }
  • AbstractAggregationBuilder:用于生成分组查询的构建器,其对象通过AggregationBuilders工具类生成
  • Pageable:表示分页参数,对象通过PageRequest.of(页数, 容量)获取
  • SortBuilder:排序构建器,对象通过SortBuilders.fieldSort(字段).order(规则)获取

实例

@SpringBootTest
class ApplicationTests {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;
	@Test
	void test1() { //插入
		User s = new User();
		s.setUid(100);
		s.setUname("mao");
		s.setAge(18L);
		s.setTel("123");
		s.setCity("沈阳");
		s.setAddress("皇姑");
		User u = (User) userRepository.save(s);
		System.out.println(u);
    }
    @Test
    void test2() { //更新  id 存在就是全文更新
        User  s=new User();
        s.setId(1000+"");
        s.setUid(200);
        s.setUname("maomaocong2");
        s.setAge(19L);
        s.setTel("123");
        s.setCity("沈阳");
        s.setAddress("上海黄浦区");
        User u = userRepository.save(s);
        System.out.println(u);
    }
    @Test
    void test3() { //删除
        User  s=new User();
        s.setId(1000+"");
        //userRepository.delete(s);
        userRepository.deleteById("2000");
        System.out.println("删除成功!");
    }
    @Test
    void test3_1() {//删除全部
        userRepository.deleteAll();
        System.out.println("删除成功!");
    }
    @Test
    void test4() {//通过id查询单个文档
        Optional<User> byId = userRepository.findById("5");
        User user = byId.get();
        System.out.println(user);
    }
    @Test
    void test4_1() {//查询全部文档
        Iterable<User> all = userRepository.findAll();
        for (User user : all) {
            System.out.println(user);
        }
    }
}
复杂查询
@SpringBootTest
class ApplicationTests {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;
	@Test
	void test4_2() {//分页,排序  按照年龄,降序排列  显示第1页, 每页显示4条 第一个参数0 表示第一页
	    Iterable<User> all = userRepository.findAll(PageRequest.of(1,4, Sort.Direction.DESC,"age"));
	    for (User user : all) {
	        System.out.println(user);
	    }
	}
	@Test
	void test4_3() {//search()方法分页
	    //拼query 查询条件的   相当于{}
	    NativeSearchQueryBuilder builder=new NativeSearchQueryBuilder();
	    //分页信息 with相当于一级属性 第一个字段,需要自己处理一下
	    builder.withPageable(PageRequest.of(0,3));
	    Page<User> users = userRepository.search(builder.build());
	    System.out.println("当前页="+users.getNumber());
	    System.out.println("每页显示多少条="+users.getSize());
	    System.out.println("总行数="+users.getTotalElements());
	    System.out.println("总页数="+users.getTotalPages());
	    System.out.println("list="+users.getContent());
	
	    System.out.println("users="+users);
	    for (User user : users) {
	        System.out.println(user);
	    }
	}
	@Test
    void test4_4() {//排序
        NativeSearchQueryBuilder builder=new NativeSearchQueryBuilder();
        builder.withSort(
                SortBuilders.fieldSort("age").order(SortOrder.DESC)
        );
        builder.withSort( //除了 text 都可以
                SortBuilders.fieldSort("uid").order(SortOrder.ASC)
        );
        Page<User> users = userRepository.search(builder.build());
       for (User user : users) {
            System.out.println(user);
        }
    }
    @Test
    void test4_5() {//精确查询
        /* kibana方式
        GET /test1/user/_search
        {
          "query": {
            "term": {
              "city": "沈阳"
            }
          }
        }
        */
        NativeSearchQueryBuilder builder=new NativeSearchQueryBuilder();
        builder.withQuery(
                QueryBuilders.termQuery("city.keyword","沈阳")
        );
        Page<User> users = userRepository.search(builder.build());
        for (User user : users) {
            System.out.println(user);
        }
    }
    @Test
    void test4_7() {//全文查找
       NativeSearchQueryBuilder builder=new NativeSearchQueryBuilder();
       builder.withQuery(
               QueryBuilders.matchQuery("city","辽阳")
       );
       Page<User> users = userRepository.search(builder.build());
       for (User user : users) {
           System.out.println(user);
       }
    }
    @Test
    void test4_8() {//多字段全文查找
        NativeSearchQueryBuilder builder=new NativeSearchQueryBuilder();
        builder.withQuery(
                QueryBuilders.multiMatchQuery("辽阳","city","address")
        );
        Page<User> users = userRepository.search(builder.build());
        for (User user : users) {
            System.out.println(user);
        }
    }
    @Test
    void test4_9() {//范围查找
        NativeSearchQueryBuilder builder=new NativeSearchQueryBuilder();
        builder.withQuery(
                QueryBuilders.rangeQuery("age").gte(15).lte(20)
        );
        Page<User> users = userRepository.search(builder.build());
        for (User user : users) {
            System.out.println(user);
        }
    }
    @Test
    void test4_10() {//逻辑运算符must
        NativeSearchQueryBuilder builder=new NativeSearchQueryBuilder();
        builder.withQuery(
	        QueryBuilders.boolQuery()
	                .must(
	                    QueryBuilders.matchQuery("city","辽阳")
	                 )
	                .must(
	                    QueryBuilders.rangeQuery("age").lte(20).gte(15)
	                )
        );
        Page<User> users = userRepository.search(builder.build());
        for (User user : users) {
            System.out.println(user);
        }
    }
    @Test
    void test4_11() {//逻辑运算符should
        NativeSearchQueryBuilder builder=new NativeSearchQueryBuilder();
        builder.withQuery(
	        QueryBuilders.boolQuery()
	                .should(
	                        QueryBuilders.matchQuery("city","辽阳")
	                )
	                .should(
	                        QueryBuilders.rangeQuery("age").lte(20).gte(15)
	                )
        );
        Page<User> users = userRepository.search(builder.build());
        for (User user : users) {
            System.out.println(user);
        }
    }
    @Test
    void test4_11() {//逻辑运算符must_not
        NativeSearchQueryBuilder builder=new NativeSearchQueryBuilder();
        builder.withQuery(
	        QueryBuilders.boolQuery()
	                 .mustNot(
	                        QueryBuilders.rangeQuery("age").lte(20).gte(15)
	                )
        );
        Page<User> users = userRepository.search(builder.build());
        for (User user : users) {
            System.out.println(user);
        }
    }
    @Test
    void test4_12() {//filter
        NativeSearchQueryBuilder builder=new NativeSearchQueryBuilder();
        builder.withQuery(
	        QueryBuilders.boolQuery()
	                .filter(
	                        QueryBuilders.rangeQuery("age").lte(20).gte(15)
	                )
        );
        Page<User> users = userRepository.search(builder.build());
        for (User user : users) {
            System.out.println(user);
        }
    }
    @Test
    void test4_13() {//分组查找:没有指标的桶
        /*
        * "aggregations" : {
		    "myaggs" : {
		      "doc_count_error_upper_bound" : 0,
		      "sum_other_doc_count" : 0,
		      "buckets" : [
		        {
		          "key" : "沈阳",
		          "doc_count" : 5
		        },
		        {
		          "key" : "天津",
		          "doc_count" : 2
		        }
		      ]
		    }
		  }
        * */
        NativeSearchQueryBuilder builder=new NativeSearchQueryBuilder();
        builder.addAggregation(AggregationBuilders.terms("mycity").field("city.keyword"));

        AggregatedPage<User> page = (AggregatedPage<User>)  userRepository.search(builder.build());

        StringTerms mycity = (StringTerms) page.getAggregation("mycity");
        List<StringTerms.Bucket> buckets = mycity.getBuckets();
        for (StringTerms.Bucket bucket : buckets) {
            System.out.println("桶="+bucket.getKey());
            System.out.println("每桶数量="+bucket.getDocCount());
        }
    }
    @Test
    void test4_14() {//分组查找:有指标的桶
        /*
        {
          "key" : "天津",
          "doc_count" : 2,
          "max_age" : {
            "value" : 25.0
          },
          "avg_age" : {
            "value" : 20.0
          },
          "min_age" : {
            "value" : 15.0
          }
        }
        * */
       NativeSearchQueryBuilder builder=new NativeSearchQueryBuilder();
       builder.addAggregation(
               AggregationBuilders.terms("mycity").field("city.keyword")
               .subAggregation(//指标  stats 里面有统计分析数据
                       AggregationBuilders.stats("ageItem").field("age")
               )
        );
        AggregatedPage<User> page = (AggregatedPage<User>)  userRepository.search(builder.build());
        StringTerms mycity = (StringTerms) page.getAggregation("mycity");
        List<StringTerms.Bucket> buckets = mycity.getBuckets();
        for (StringTerms.Bucket bucket : buckets) {
            System.out.println("桶="+bucket.getKey()+"===="+"每桶数量="+bucket.getDocCount());
            InternalStats statsAge = bucket.getAggregations().get("ageItem");
            System.out.println(statsAge.getSumAsString());
            System.out.println(statsAge.getAvgAsString());
            System.out.println(statsAge.getMaxAsString());
            System.out.println(statsAge.getMinAsString());
            System.out.println(statsAge.getCount()); //去除空值的数
        }
    }
    @Test
    void test4_15() {//高亮显示,使用ElasticsearchTemplate
        ObjectMapper mapper = new ObjectMapper(); //json和对象之间的转换. 类似于fastjson
        NativeSearchQueryBuilder builder=new NativeSearchQueryBuilder();
        //指定索引库和type数据
        builder.withIndices("test1").withTypes("user");
        //全文检索
        builder.withQuery(QueryBuilders.multiMatchQuery("辽阳","city","address"));
        //设置属性的高亮配置
        builder.withHighlightFields(
	        new HighlightBuilder.Field("city")
	                .preTags("<span style='color:red'>").postTags("</span>"),
	        new HighlightBuilder.Field("address")
	                .preTags("<span style='color:red'>").postTags("</span>")
        );
        //调用queryForPage 参数1 是NativeSearchQuery对象,参数2 User的类型, 参数3 查询结果的映射器
        //从ES取出数据,封装到List<User>对象中,还有分页相关信息
        AggregatedPage<User> users = elasticsearchTemplate.queryForPage(builder.build(), User.class, new SearchResultMapper() {
            @Override //需要对结果进行映射
            public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
                List<T> list = new ArrayList<>();
                for (SearchHit hit : searchResponse.getHits().getHits()) {
                    try{
                        // 把一个json字符串,转换为java对象
                        T t = mapper.readValue(hit.getSourceAsString(), aClass);
                        // hit.getHighlightFields().values() 代表所有高亮显示的属性部分   city, address
                        for (HighlightField field : hit.getHighlightFields().values()) {
                            // 替换需要高亮显示的字段,用到Apache的BeanUtils工具 把t中的 city和address 内容替换掉
                            //  参数1 表示要替换属性的对象,替换的属性名称,替换的属性值
                            BeanUtils.setProperty(t, field.getName(), field.getFragments()[0].string());
                        }
                        list.add(t);
                    }catch(Exception e){
                        e.printStackTrace();
                    }
                }
                long total = searchResponse.getHits().totalHits;
                AggregatedPageImpl ret=new AggregatedPageImpl<>(list, pageable, total);
                return ret;
            }
            @Override
            public <T> T mapSearchHit(SearchHit searchHit, Class<T> aClass) {
                return null;
            }
        });
        //遍历显示信息
        for (User user : users) {
            System.out.println(user);
        }
    }
    @Test
    void test5() {//批量导入
        int counter = 0;
        try {
            //判断索引是否存在
            if(!elasticsearchTemplate.indexExists("test1")){
                elasticsearchTemplate.createIndex("test1");
            }
            ObjectMapper mapper = new ObjectMapper();
          	//要添加的数据
            List<User> users = new ArrayList<>();
            User u1=new User();
            u1.setId("4001");
            u1.setUid(301);
            u1.setUname("mao1");
            u1.setCity("大连");
            users.add(u1);
            User u2=new User();
            u2.setId("4002");
            u2.setUid(302);
            u2.setUname("mao2");
            u2.setCity("大连");
            users.add(u2);
            //队列 放入批量{ }{"create":{"_id":101}}
            //{"doc":{"name":"maomao1","age":21}}
            List<IndexQuery> queries = new ArrayList<>();
            for (User u : users) {
                IndexQuery indexQuery = new IndexQuery();//{}
                indexQuery.setIndexName("test1");
                indexQuery.setType("user");
                indexQuery.setId(u.getId().toString());
                indexQuery.setSource(mapper.writeValueAsString(u));
                queries.add(indexQuery);
                counter++;
                //分批提交索引
                if (counter % 500 == 0) {
                    elasticsearchTemplate.bulkIndex(queries);
                    queries.clear();
                    System.out.println("bulkIndex counter : " + counter);
                }
            }
            //不足批的索引最后不要忘记提交
            if (queries.size() > 0) {
                   elasticsearchTemplate.bulkIndex(queries);
            }
            elasticsearchTemplate.refresh("test1");
            System.out.println("bulkIndex completed.");
        } catch (Exception e) {
            System.out.println("IndexerService.bulkIndex e;" + e.getMessage());
            e.printStackTrace();
        }
    }
}
// 注:同步索引库的时候, 先删除,再批量增加,不要做更新
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值