一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情。
1.Elaticsearch 简介
Elaticsearch,简称为ES,ES是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检 索数据;本身扩展性很好,可以扩展到上百台服务器,处理 PB 级别(大数据时代)的数据。ES由 Java 语言开发并使用 Lucene 作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的 RESTFULL API 来隐藏 Lucene 的复杂性,从而让全文搜索变得简单
2.ES 的下载安装(Linux 下安装)
要求:JDK版本最低为1.8 ,系统中已经配置好 JAVA 环境;
官网地址:www.elastic.co/cn/
官方文档地址:www.elastic.co/guide/en/el…
下载地址:www.elastic.co/cn/download…
3. ES 的分词器插件
ES 自己默认带有分词器,但是支持的是英文分词,所以我们要安装一个可以对中文分词的插件
3.1 IK 分词器下载
IK 分词器 Github 地址:github.com/medcl/elast…
4. ES中基本概念
4.1 接近实时(NRT Near Real Time )
Elasticsearch是一个接近实时的搜索平台。这意味着,从索引一个文档直到这个文档能够被搜索到有一 个轻微的延迟(通常是1秒内)
4.2 索引(index)
一个索引就是一个拥有几分相似特征的文档的集合。比如说,你可以有一个客户数据的索引,另一个产 品目录的索引,还有一个订单数据的索引。一个索引由一个名字来标识(必须全部是小写字母的),并且 当我们要对这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。索引类似于 关系型数据库中Database 的概念。在一个集群中,如果你想,可以定义任意多的索引。
4.3 类型(type)
在一个索引中,你可以定义一种或多种类型。一个类型是你的索引的一个逻辑上的分类/分区,其语义完 全由你来定。通常,会为具有一组共同字段的文档定义一个类型。比如说,我们假设你运营一个博客平 台并且将你所有的数 据存储到一个索引中。在这个索引中,你可以为用户数据定义一个类型,为博客数 据定义另一个类型,当然,也可 以为评论数据定义另一个类型。类型类似于关系型数据库中Table的概 念。 NOTE: 在5.x版本以前可以在一个索引中定义多个类型,6.x之后版本也可以使用,但是不推荐,在7~8.x版本 中彻底移除一个索引中创建多个类型。
4.4 映射(Mapping)
Mapping是ES中的一个很重要的内容,它类似于传统关系型数据中table的schema,用于定义一个索 引(index)中的类型(type)的数据的结构。 在ES中,我们可以手动创建type(相当于table)和mapping(相 关与schema),也可以采用默认创建方式。在默认配置下,ES可以根据插入的数据自动地创建type及其 mapping。 mapping中主要包括字段名、字段数据类型和字段索引类型。
4.5 文档(document)
一个文档是一个可被索引的基础信息单元,类似于表中的一条记录。比如,你可以拥有某一个员工的文 档,也可以拥有某个商品的一个文档。文档以采用了轻量级的数据交换格式JSON(Javascript Object Notation)来表示。
5. SpringBoot 整合 ES
5.1 引入依赖
pom.xml 中引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
<version>2.4.2</version>
</dependency>
5.2 添加 ES 相关配置
application.yml 中配置
spring:
elasticsearch:
rest:
# ES集群节点url地址,多个英文逗号隔开
uris:
http://10.252.249.44:9201,http://10.252.249.44:9202,http://10.252.249.44:9203
5.3 相关注解介绍
ES 几个常用注解
@Document :声明索引库配置
indexName :索引库名称
type :映射类型。如果未设置,则使用小写的类的简单名称。(从版本4.0开始不推荐使
用)
shards :分片数量,默认 5
replicas :副本数量,默认 1
@Id :声明实体类的
@Field :声明字段属性
type :字段的数据类型
analyzer :指定在存储时候使用的分词器类型
searchAnalyzer :指定在搜索时候使用的分词器类型
index :是否创建索引
5.4 实体类
package com.daqsoft.study.elasticsearch.entity;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.*;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @author taner
* @version 1.0.0
* @since JDK 1.8
*/
@Data
// indexName 只能小写
@Document(indexName = "people", shards = 5, replicas = 1)
public class People implements Serializable {
/**
* 数据id
*/
@Id
private Long id;
/**
* 名称
*/
@Field(type = FieldType.Text, searchAnalyzer = "ik_max_word", analyzer = "ik_max_word")
private String name;
/**
* 性别
*/
@Field(type = FieldType.Integer)
private Integer sex;
/**
* 电话
*/
@Field(type = FieldType.Keyword)
private String phone;
/**
* 年龄
*/
@Field(type = FieldType.Integer)
private Integer age;
/**
* 创建时间
*/
@Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createDate;
/**
* 当前位置
*/
@GeoPointField
private GeoPoint location;
}
5.5 Repository 接口
package com.daqsoft.study.elasticsearch;
import com.daqsoft.study.elasticsearch.entity.People;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
/**
* @author taner
* @version 1.0.0
* @since JDK 1.8
*/
@Repository
public interface PeopleRepository extends ElasticsearchRepository<People, Long>
{
}
5.6 SpringBoot 操作ES增删改
package com.daqsoft.study;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.RandomUtil;
import com.daqsoft.study.elasticsearch.PeopleRepository;
import com.daqsoft.study.elasticsearch.entity.People;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import
org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
class StudyElasticsearchApplicationTests {
@Resource
private PeopleRepository peopleRepository;
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
@Test
void contextLoads() {
}
/**
* 测试插入数据
*/
@Test
void testInsert() {
People people = new People();
people.setId(1L);
people.setName("张三");
people.setAge(25);
people.setPhone("18883842027");
people.setCreateDate(LocalDateTime.now());
people.setLocation(new GeoPoint(30.001, 100.005));
peopleRepository.save(people);
}
/**
* 测试批量插入数据
*/
@Test
void testInsertList() {
List<People> peopleList = new ArrayList<>();
double latitude = 30.00d, longitude = 100.00d;
for (long id = 1L; id <= 100; id++) {
People people = new People();
people.setId(id);
people.setName(generateName());
people.setAge(RandomUtil.randomInt(20, 30));
people.setPhone(generatePhone());
if (RandomUtil.randomInt(100) % 3 != 0) {
people.setSex(RandomUtil.randomInt(2));
}
people.setCreateDate(LocalDateTime.now().plusDays(RandomUtil.randomInt(-30,
30)));
people.setLocation(new GeoPoint(latitude +
RandomUtil.randomDouble(-0.3, 0.3), longitude + RandomUtil.randomDouble(-0.3,
0.3)));
peopleList.add(people);
}
peopleRepository.saveAll(peopleList);
}
/**
* 测试查询所有
*/
@Test
void testSelectAll() {
Iterable<People> iterable = peopleRepository.findAll();
iterable.forEach(System.out::println);
}
/**
* 测试查询数据
*/
@Test
void testSelect() {
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 单条件查询
// queryBuilder.withQuery(QueryBuilders.termQuery("age", 25));
// 多条件查询
queryBuilder.withQuery(QueryBuilders.boolQuery()
// 精确查询
.must(QueryBuilders.termQuery("sex", 1))
// 范围查询
.must(QueryBuilders.rangeQuery("age").lt(25).gt(22))
// 分词查询
.must(QueryBuilders.matchQuery("name", "张,三"))
// 前缀查询
.must(QueryBuilders.prefixQuery("phone", "182"))
);
// 地理位置范围查询 查询地理位置30.00d, 100.00d 10KM范围的数据
queryBuilder.withFilter(QueryBuilders.geoDistanceQuery("location")
.point(30.00d, 100.00d)
.distance(100, DistanceUnit.KILOMETERS));
queryBuilder.withHighlightFields(new
HighlightBuilder.Field("name").preTags("<em>").postTags("</em>"));
// 排序 按照年龄升序排列
queryBuilder.withSort(SortBuilders.fieldSort("age").order(SortOrder.ASC));
// 排序 以30.00d,100.00d为中心,按距离升序
queryBuilder.withSort(SortBuilders.geoDistanceSort("location", 30.00d,
100.00d).order(SortOrder.ASC));
// 分页查询 page:页码(大于等于0) size:每页数量(大于0)
queryBuilder.withPageable(PageRequest.of(0, 10));
SearchHits<People> searchHits =
elasticsearchRestTemplate.search(queryBuilder.build(), People.class);
searchHits.get().forEach(itm -> {
People people = itm.getContent();
List<String> name = itm.getHighlightField("name");
if (CollectionUtil.isNotEmpty(name)) {
people.setName(name.get(0));
}
System.out.println(people);
});
}
@Test
void testDelete() {
// 通过id删除数据
elasticsearchRestTemplate.delete("1", People.class);
// 通过条件删除数据
elasticsearchRestTemplate.delete(new
NativeSearchQueryBuilder().withQuery(QueryBuilders.termQuery("age",
25)).build(), People.class);
}
/**
* 生成随机名称
*
* @return 随机名称
*/
public static String generateName() {
String[] namePrefix = new String[]{"赵", "钱", "孙", "李", "王", "张", "令
狐", "慕容", "周", "黄"};
String[] nameSuffix = new String[]{"一", "二", "三", "四", "五", "六",
"七", "八", "九", "零"};
return namePrefix[RandomUtil.randomInt(namePrefix.length)] +
nameSuffix[RandomUtil.randomInt(nameSuffix.length)];
}
/**
* 生成电话号码
*
* @return 随机号码
*/
public static String generatePhone() {
String[] phonePrefix = new String[]{"182", "183", "184"};
return phonePrefix[RandomUtil.randomInt(phonePrefix.length)] +
RandomUtil.randomInt(10000000, 100000000);
}
}