what:ElasticSearch是什么
Elasticsearch 是一个实时的分布式存储、搜索、分析的引擎
在当今分布式架构的潮流下,elasticsearch变得越来越受欢迎
why:为什么要用ElasticSearch
应用场景
- 搜索领域:百度、谷歌等搜索企业
- 门户网站:访问统计、文章点赞、留言评论等
- 广告推广:记录用户行为数据、消费趋势等
- 信息采集:记录应用埋点数据,访问数据等
优点优势
相较于传统的数据库搜索、elasticsearch的搜索能力在于能够进行模糊查询。(此模糊非彼模糊)
传统的模糊匹配都是使用like关键字来进行搜索 ,例如搜索名字带王的数据
select * from db where db_name like '%王%'
缺点:1、不走索引。 2、同时会查询到大量数据。3、假定用户输入了错别字但是还是想查询到正确的结果,这时候传统的查询做不到
这时候elasticsearch的优点就体现出来了。
- Elasticsearch对模糊搜索非常擅长(搜索速度很快)
- 从Elasticsearch搜索到的数据可以根据点赞或者浏览数量较多的来过滤掉大部分的,只要返回评分高的给用户就好了。
- 没有那么准确的关键字也能搜出相关的结果(能匹配有相关性的记录)
ElasticSearch数据结构
以上图为例子,一个文档代表一个员工数据。存储数据到es中的行为叫索引,但在索引一个文档之前需要知道其存储在哪里。
对比数据库
- 索引 -〉数据库
- 类型 -〉表
- 文档 -〉表中的一行记录
- 属性 -〉字段列
Elasticsearch集群可以包含多个索引,每个索引可以包含多个类型,每个类型可以包含多个文档,然后每个文档又可以包含多个字段。这与数据库的结构类似,在DB中可以有多个数据库,每个库中可以有多张表,每个表中又包含多行,每行包含多列。
术语
1、索引
索引是含义相同的属性文档的集合,是Elasticsearch的一个逻辑存储,可以理解为关系型数据库中的数据库。Elasticsearch可以把索引数据存放到一台服务器上,也可以分片后存到多台服务器上,每个索引有一个或多个分片,每个分片可以有多个副本。
索引也可以定义一个或多个类型,文档必须属于一个类型。在Elasticsearch中,一个索引对象可以存储多个不同用途的对象,通过索引类型可以区分单个索引中的不同对象,可以理解为关系型数据库中的表。每个索引类型可以有不同的结构,但是不同的索引类型不能为相同的属性设置不同的类型。
2、类型
文档可以分组,比如员工信息。这种分组就叫类型,它是虚拟的逻辑分组,用来过滤文档数据。不同的类型应该有相似的数据结构(schema),例如,id字段不能在这个组是字符串,在另一个组是数值,这是与关系型数据库的表的一个区别。结构完全不同的数据(比如products和orders)应该存成两个索引,而不是一个索引的两个类型。
3、文档
文档是可以被索引的基本数据单位,存储在Elasticsearch中的主要实体叫文档,可以理解为关系型数据库中表的一行记录。每个文档由多个字段构成,Elasticsearch是一个非结构化的数据库,每个文档可以有不同的字段,并且有一个唯一的标识符。
HOW:使用ElasticSearch
SpringBoot集成ElasticSearch
- Repositories:继承自Spring Data中的Repository接口,所以支持以数据库的方式对数据进行增删改查的操作,而且支持已命名查询等数据查询
- ElasticsearchRestTemplate:spring-data-elasticsearch项目中的一个类,和其他Spring项目中的Template类似。ElasticsearchRestTemplate是Spring对ES的Rest API进行的封装,支持复杂的数据查询、统计功能。
1、添加pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
2、修改配置文件
spring:
elasticsearch:
uris: https://es-92vjur7l.public.tencentelasticsearch.com:9200
username: elasticxxxx
password: xxxxxx
#这里连接云服务器。
3、创建文档对象
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "book")
public class EBook {
@Id
private Long id;
@Field(analyzer = "ik_max_word" , type = FieldType.Text)
private String bookName;
@Field(type = FieldType.Keyword)
private String author;
private double price;
private int page;
@Field(type = FieldType.Keyword , fielddata = true)
private String category;
}
1)@Document注解会对实体中的所有属性建立索引:
indexName = “customer”:表示创建一个名为customer的索引。
type=“customer”:表示在索引中创建一个名为customer的类别,而在Elasticsearch 7.x版本中取消了类别的概念。
shards = 1:表示只使用一个分片,默认为5。
replicas = 0:表示副本数量,默认为1,0表示不使用副本。
refreshInterval = “-1”:表示禁止索引刷新。
2)@Id作用在成员变量,标记一个字段作为id主键。
3)@Field作用在成员变量,标记为文档的字段,并指定字段映射属性:
type:字段类型,取值是枚举:FieldType。
index:是否索引,布尔类型,默认是true。
store:是否存储,布尔类型,默认是false。
analyzer:分词器名称是ik_max_word。
4、创建操作的repository
public interface BookRepository extends ElasticsearchRepository<EBook , Integer> {
List<EBook> finaByBookNameLike(String bookName);
}
5、创建测试
@Test
public void test(){
EBook eBook = EBook.builder().id(1L).bookName("测试检索").author("test").price(45.6).category("advantrue").page(1).build();
bookRepository.save(eBook);
EBook newBook = bookRepository.findById(1L)
.orElseThrow(()->new RuntimeException("detail not find"));
System.out.println(newBook);
}
/**
结果:
EBook(id=1, bookName=测试检索, author=test, price=45.6, page=1, category=advantrue)
*/
ElasticSearchRepository操作ES
Spring Boot提供了spring-boot-starter-data-elasticsearch组件来集成Elasticsearch服务,通过名字就可以看出,它也是Spring Data中的成员。因此,ElasticsearchRepository与之前的JpaRepository使用方法类似,只需要简单继承ElasticsearchRepository就可以实现对Elasticsearch数据的操作。
以下是UML类图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9RUHz7pH-1655727379968)(/Users/ozzo/Library/Application Support/typora-user-images/image-20220620140143924.png)]
通过UML类图可以查看跟JPA类似的结构,同时也支持相应的增删改查接口。(由于简单这里不做演示)
1、分页查询
分页查询有两种实现方式:一种是使用Spring Data自带的分页方案,另一种是QueryBuilder自行组织查询条件,再封装进行查询。
1)Pageable分页
EBook eBook1 = EBook.builder().id(1L).bookName("测试检索2").author("test").price(49.6).category("roman").page(1).build(); bookRepository.save(eBook1); EBook eBook2 = EBook.builder().id(2L).bookName("测试检索1").author("test").price(60.6).category("advantrue").page(1).build(); bookRepository.save(eBook2); EBook eBook3 = EBook.builder().id(3L).bookName("测试检索3").author("test").price(45.6).category("science").page(1).build(); bookRepository.save(eBook3); Sort sort = Sort.by(Sort.Direction.DESC,"price"); Pageable pageable = PageRequest.of(0,4,sort); bookRepository.findAll(pageable).forEach(System.out::println);
2)QueryBuilder查询
1、匹配
QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("name","Object")); /** QueryBuilders包含的一些方法与AND、OR、NOT一一对应:Must(QueryBuilders)对应AND。mustNot(QueryBuilders)对应NOT。should对应OR */
2、精准匹配
/** * 单条件匹配 * */ //不分词沙逊,参数1:字段名字,参数2:字段查询值。 QueryBuilders.termQuery(); //分词查询 QueryBuilders.matchQuery(); /** * 多条件匹配 * */ //不分词。参数1:字段名,参数2:多个字段 QueryBuilders.termsQuery("author","z","d"); //分词查询。采用默认分词 QueryBuilders.multiMatchQuery("");
3、模糊匹配
/** * 模糊查询 * */ //左右模糊 QueryBuilders.queryStringQuery("").field(""); //前缀查询 QueryBuilders.prefixQuery("",""); //分词模糊查询 fuzziness含义是检索trtm前后增加或减少n哥单词的匹配查询 QueryBuilders.fuzzyQuery("","").fuzziness(Fuzziness.ONE);
4、通配符
/** * 通配符查询,使用wildcardQuery进行通配符查询。 * "*"表示任意字符串,"?"表示任意一个字符 * */ QueryBuilders.wildcardQuery("bookName","d*"); QueryBuilders.wildcardQuery("bookName","d?");
5、范围查询
/** * 范围查询 * */ //默认闭区间这里api与Mybatis-Plus很类似,详细可以参考mp的官方文档 //这里给一个例子 QueryBuilders.rangeQuery("price").from(20).to(100);
ElasticsearchRestTemplate
1、简介
ElasticsearchRestTemplate是Spring Data Elasticsearch提供的Elasticsearch数据操作类,基于Elasticsearch的HTTP协议,用来取代之前的TransportClient客户端。ElasticsearchRestTemplate的目的是让我们用更简单的方式实现复杂的Elasticsearch数据查询操作。
操作
1、创建文档(index方法)
private final static String INDEX_OF_BOOK = "book";
@Autowired
ElasticsearchRestTemplate restTemplate;
@Test
public void test(){
EBook eBook = EBook.builder()
.bookName("斗破苍穹")
.author("天蚕土豆")
.id(1L)
.page(1700)
.category("玄幻").build();
IndexQuery indexQuery = new IndexQueryBuilder()
.withId(eBook.getId().toString())
.withObject(eBook).build();
//创建文档
String index = restTemplate.index(indexQuery, IndexCoordinates.of(INDEX_OF_BOOK));
System.out.println(index);
}
批量创建文档(blukindex方法)
private final static String INDEX_OF_BOOK = "book";
@Autowired
ElasticsearchRestTemplate restTemplate;
@Test
public void test(){
//这里跟上面创建的都是使用了Lombok的builder注解 详细了解(建造者模式)
EBook eBook = EBook.builder()
.bookName("斗破苍穹")
.author("天蚕土豆")
.id(1L)
.page(1700)
.category("玄幻").build();
EBook dBook = EBook.builder()
.bookName("我是传奇")
.page(12400)
.id(2L)
.author("davinc")
.category("judege").build();
//批量创建
List<EBook> eBookList = new ArrayList<>();
eBookList.add(eBook);
eBookList.add(dBook);
List<IndexQuery> indexQueryList = eBookList.stream()
.map(x -> new IndexQueryBuilder().withId(x.getId().toString()).withObject(x).build())
.toList();
//获取文档索引
List<IndexedObjectInformation> documentId = restTemplate.bulkIndex(indexQueryList,IndexCoordinates.of(INDEX_OF_BOOK));
documentId.stream().map(IndexedObjectInformation::getId).forEach(System.out::println);
2、查询文档
ElasticsearchRestTemplate通过search()方法实现非常完善的文档查询功能。它的使用方式与ElasticsearchRepository的query()类似。首先构建QueryBuilder对象,然后将查询对象传入search()方法执行查询。ElasticsearchRestTemplate提供了NativeQuery、StringQuery和CriteriaQuery,通过这3个Query构造各种文档查询方式。
- NativeQuery:可以灵活地构建各种复查查询(如聚合、筛选和排序)。
- StringQuery:使用JSON字符串来构建查询条件,和Repository中的@Query注解中的JSON字符串类似。
- CriteriaQuery:通过简单地连接和组合所要搜索的文档必须满足指定的条件来生成查询,而无须了解Elasticsearch查询的语法或基础知识。
1)、NativeQuery:
/** * NativeQuery:使用 通过QueryBuilder构建category字段包含“历史”关键字的查询条件,然后使用NativeSearchQueryBuilder构建NativeQuery,最后执行search()方法 * */ QueryBuilder builder = QueryBuilders.matchQuery("category", "玄幻"); Query searchQuery = new NativeSearchQueryBuilder().withQuery(builder).build(); SearchHits<EBook> search = restTemplate.search(searchQuery, EBook.class, IndexCoordinates.of(INDEX_OF_BOOK)); search.getSearchHits().forEach(System.out::println); /** 结果: / SearchHit{id='1', score=0.9808291, sortValues=[], content=EBook(id=1, bookName=斗破苍穹, author=天蚕土豆, price=0.0, page=1700, category=玄幻), highlightFields={}} */
2)、StringQuery:
/** * StringQuery:传入json查询条件 * */ Map<String , String> map = new HashMap(); map.put("bookName" , "我是传奇"); Map<String,Object> param = new HashMap<>(); param.put("match",map); //hutool转换json JSON parse = JSONUtil.parse(param); //转换json对象搜索 Query stringQuery = new StringQuery(parse.toString()); SearchHits<EBook> search = restTemplate.search(stringQuery, EBook.class, IndexCoordinates.of(INDEX_OF_BOOK)); search.stream().forEach(System.out::println); /** 结果: SearchHit{id='2', score=2.9424872, sortValues=[], content=EBook(id=2, bookName=我是传奇, author=davinc, price=0.0, page=12400, category=judege), highlightFields={}} */
3)、CriteriaQuery:
/** * CriteriaQuery:通过where、is、contains、and、or等简单地连接和组合所要搜索的文档必须满足指定的条件来生成查询,而无须了解Elasticsearch查询的语法或基础知识。 * */ //这里构造条件 Criteria contains = Criteria.where("bookName").contains("斗").or("author").contains("d"); CriteriaQuery criteriaQuery = new CriteriaQuery(contains); SearchHits<EBook> search = restTemplate.search(criteriaQuery, EBook.class); search.stream().forEach(System.out::println); /** 结果: SearchHit{id='1', score=1.0, sortValues=[], content=EBook(id=1, bookName=斗破苍穹, author=天蚕土豆, price=0.0, page=1700, category=玄幻), highlightFields={}} SearchHit{id='2', score=1.0, sortValues=[], content=EBook(id=2, bookName=我是传奇, author=davinc, price=0.0, page=12400, category=judege), highlightFields={}} */
4)高亮显示
通过withHightLightBuilder()来显示高亮。
3、删除文档。
删除文档比较简单这里贴出代码。
String delete = restTemplate.delete("1", IndexCoordinates.of(INDEX_OF_BOOK));
System.out.println(delete);
TermQueryBuilder builder = QueryBuilders.termQuery("author", "天蚕土豆");
NativeSearchQuery build = new NativeSearchQueryBuilder().withQuery(builder).build();
SearchHits<EBook> search = restTemplate.search(build, EBook.class, IndexCoordinates.of(INDEX_OF_BOOK));
search.get().forEach(System.out::println);
/**
结果为:
*/
4、更新文档。
EBook build = EBook.builder()
.id(2L)
.page(234200)
.category("变化了")
.author("压力山大")
.bookName("我是传奇了")
.price(46.78)
.build();
//构造更新条件
UpdateQuery updateQuery = UpdateQuery.builder("2")
//不在就新增
.withDocAsUpsert(true)
.withDocument(Document.parse(JSONUtil.parse(build).toString())).build();
UpdateResponse update = restTemplate.update(updateQuery, IndexCoordinates.of(INDEX_OF_BOOK));
EBook not_exist = bookRepository.findById(2L)
.orElseThrow(() -> new RuntimeException("not exist"));
System.out.println("第一次打印:"+not_exist);
/**
*/
聚合查询:
聚合(Aggregation)是Elasticsearch非常重要的功能,它允许用户在数据上生成复杂的分析统计。它很像SQL中的GROUP BY,但是比GROUP BY的功能更强大。在解释什么是聚合之前,首先需要了解两个重要概念:
1)Buckets(桶):满足某个条件的文档集合。我们可以将文档数据按照一个或多个条件进行划分,比如按小时、按年龄区间、按地理位置等。当然,还有更加复杂的聚合,比如先将文档按天进行分桶,再将每天的桶按小时分桶。
2)Metrics(指标):对某个桶中的文档计算得到的统计信息,比如使用min、mean、max、sum等计算得到的统计信息。概括起来,聚合就是由一个或者多个桶、一个或者多个指标组合而成的数据查询统计功能。一个聚合查询可以只有一个桶或者一个指标,或者每样一个。在桶中甚至可以有多个嵌套的桶。比如,我们可以将文档按照其所属国家/地区进行分桶,再按照年龄分桶,最后对每个桶计算其平均薪资(一个指标)。
TermsAggregationBuilder field = AggregationBuilders.terms("category").field("category.keyword"); Query build = new NativeSearchQueryBuilder().withAggregations(field).build(); SearchHits<EBook> search = restTemplate.search(build, EBook.class,IndexCoordinates.of(INDEX_OF_BOOK)); System.out.println(search.hasAggregations()); Aggregations aggregations = (Aggregations) search.getAggregations().aggregations(); aggregations.asList().forEach(x->((Terms)x).getBuckets().forEach(y-> System.out.println("分组:"+y.getKeyAsString()+":"+y.getDocCount())));
历程
感觉理论还是优点不足。后序会继续补充一下理论。(吐槽。ES版本迭代还是优点快的。。这里看了几个博客还是有点懵懵懂懂的)
后序继续更新😊