本文主要讲解Layblog博客社区整合Elasticsearch的过程,以实现Layblog站内帖子文章信息在Elasticsearch初始化、查询。
✔本文主要涵盖以下内容:
1️⃣ Elasticsearch介绍
2️⃣ Elasticsearch安装和使用
3️⃣ Elasticsearch中文分词器使用
4️⃣ 集成spring-data-elasticsearch,分析核心注解、接口
5️⃣ ElasticSearch项目实战
一、ElasticSearch介绍
Elasticsearch是一个实时分布式搜索和分析引擎。它的内部使用 Lucene 做索引与搜索,但是它的目的是使全文检索变得简单, 通过隐藏 Lucene 的复杂性,取而代之的提供一套简单一致的 RESTful API。
然而ElasticSearch不仅仅是Lucene,并且不仅仅知识一个搜索引擎,它是这样被定义的:
- 一个分布式的实时文档存储,每个字段都可以被索引和搜索。
- 一个分布式实时分析搜索引擎。
- 能胜任上百个服务节点的扩展,并支持PB级别的结构化或者非结构化数据搜索(天生支持集群)。
二、Elasticsearch安装和使用
Layblog采用的ES版本是ElasticSearch6.4.3版本,通过Docker命令拉取安装:
# 拉取es镜像并安装
docker run -p 9200:9200 -p 9300:9300 -d --name layes -e ES_JAVA_OPTS="-Xms256m -Xmx256m" elasticsearch:6.4.3
# 进入layes镜像
docker exec -it layes /bin/bash
# 进入es配置文件位置,修改es的集群名称,默认为docker-cluster,这里修改为kobe
/usr/share/elasticsearch/config/elasticsearch.yml
安装成功后,访问地址:http://112.124.38.197:9200/
三、安装中文分词器
执行以下命令拉取安装中文分词器:
./bin/elasticsearch-plugin install github.com/medcl/elast…
退出并重启layes镜像
- exit
- docker restart layes
四、集成spring-data-elasticsearch
Spring Data Elasticsearch 项目提供与 Elasticsearch 搜索引擎开发解决方案,Spring Data ElasticSearch为开发者提供:
- 模板作为存储、搜索、排序文档和构建聚合的高级抽象。
- 开发者可以通过自定义具体的方法和接口来实现查询。
Spring Data Elasticsearch - 参考文档
在pom.xml添加相关依赖
<!--Elasticsearch-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch<artifactId>
</dependency>
4.1核心接口CrudRepository
Spring Data 存储库抽象中的中央接口是Repository. 它需要域类来管理以及域类的 ID 类型作为类型参数。此接口主要用作标记接口,以捕获要使用的类型并帮助您发现扩展此接口的接口。该CrudRepository接口为被管理的实体类提供了复杂的CRUD功能。
public interface CrudRepository<T, ID> extends Repository<T, ID> {
//保存传入的实体数据
<S extends T> S save(S entity);
//批量保存传入的实体数据
<S extends T> Iterable<S> saveAll(Iterable<S> entities);
//返回指定ID表示的实体
Optional<T> findById(ID id);
//判断是否存在对应实体
boolean existsById(ID id);
//返回所有的实体
Iterable<T> findAll();
//根据ids批量查询实体数据(返回多个)
Iterable<T> findAllById(Iterable<ID> ids);
//返回实体数量
long count();
//根据ID删除实体
void deleteById(ID id);
//删除实体
void delete(T entity);
//根据传入实体删除全部实体
void deleteAll(Iterable<? extends T> entities);
//删除所有
void deleteAll();
}
4.2 核心接口PagingAndSortingRepository
PagingAndSortingRepository是CrudRespository的扩展以提供额外的方式实现对实体的分页和排序功能。
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
/**
* 返回按给定选项排序的所有实体
*/
Iterable<T> findAll(Sort sort);
/**
* 分页,返回满足Pageable对象中提供的Page限制的实体页面
*/
Page<T> findAll(Pageable pageable);
}
比如要实现用户User查询分页,可以这样实现:
PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(PageRequest.of(1, 20));
4.3 常见注解
- @Document
Document被称为文档,程序中大多的实体或对象能够被序列化为包含键值对的JSON对象,键(key)是字段(field)或属性(property)的名字,值(value)可以是字符串、数字、波尔类型、另一个对象、值数组或者其他特殊类型,比如表示日期的字符串或者表示地理位置的对象。
通常,我们可以认为对象(object)和文档(document)是等价相通的。不过,他们还是有所差别:对象(Object)是一个JSON结构体——类似于哈希、hashmap、字典或者关联数组;对象(Object)中还可能包含其他对象(Object)。Elasticsearch中,文档(document)这个术语有着特殊含义。它特指最顶层结构或者根对象(root object)序列化成的JSON数据(以唯一ID标识并存储于Elasticsearch中)。
//表示映射到Elasticsearch文档上的领域对象
public @interface Document {
//索引库名次,mysql中数据库的概念
String indexName();
//文档类型,mysql表的概念
String type() default "";
//默认分片数
short shards() default 5;
//默认副本数
short replicas() default 1;
...
}
- @Field
public @interface Field {
//文档中字段的类型
FieldType type() default FieldType.Auto;
//是否建立倒排索引
boolean index() default true;
//是否进行存储
boolean store() default false;
//分词器名次
String analyzer() default "";
}
//为文档自动指定元数据类型
public enum FieldType {
Text,//会进行分词并建了索引的字符类型
Integer,
Long,
Date,
Float,
Double,
Boolean,
Object,
Auto,//自动判断字段类型
Nested,//嵌套对象类型
Ip,
Attachment,
Keyword//不会进行分词建立索引的类型
}
五、Layblog之ElasticSearch实战
5.1 定义PostDocument文档
/**
* model的内容根据文章列表的展示提取需要字段,需要标题,作者,作者id,VIP,分类,阅读数量,评论数量,创建时间等等...
*
* @Author 林必昭
* @Date 2022/2/7 10:53
*/
@Data
@Document(indexName = "post", type = "post") //indexName为索引库名:建议使用项目名称命名,type:类型,个人建议以实体类名称命名
public class PostDocument implements Serializable {
@Id
private Long id;
/**
* 索引时,为了提供索引覆盖范围,通常analyzer采用ik_max_word分析器,以最细颗粒度分词索引,这样可以搜索的词语更多,
* searchAnalyzer表示搜索时候的分词,搜索时为了提高搜索准确度,会采用ik_smart分析器,会以粗粒度分词
*/
@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
private String title;
private Long authorId;
@Field(type = FieldType.Keyword)
private String authorName;
private String authorVip;
private String authorAvatar;
private Long categoryId;
/**
* FieldType.Keyword需要完全匹配,不进行分词,直接索引
*/
@Field(type = FieldType.Keyword)
private String categoryName;
private Boolean recommend;
private Integer level;
/**
* FieldType.Text表示文本,需要经过分词,然后进行索引
*/
@Field(type = FieldType.Text)
private String tags;
private Integer commentCount;
private Integer viewCount;
private Date created;
}
5.2 定义PostRepository继承ElasticsearchRepository获取常用的数据操作方法
/**
* 只需要继承ElasticsearchRepository,调用其search()方法,接口之间的继承关系:
*
* @Index——>Repository——>CrudRepository——>PageAndSortRepository——>ElasticSearchCrudRepository——>ElasticSearchRepository
* @Author 林必昭
* @Date 2022/2/7 10:52
*/
@Repository
public interface PostRepository extends ElasticsearchRepository<PostDocument, Long> {
}
在接口内可以衍生对应的查询:
比如定义根据作者名称检索文章信息方法:
/**
* 根据作者查询文章信息
*
* @author: jacklin
* @since 2022/8/10 23:42
**/
Page<PostDocument> findPostDocumentByAuthorName(String authorName);
5.3 定义search接口
/**
* 搜索方法接口
*
* @param current
* @param size
* @param q
* @return
*/
@RequestMapping("/search")
public String search(@RequestParam(defaultValue = "1") Integer current,
@RequestParam(defaultValue = "10") Integer size,
String q) {
com.baomidou.mybatisplus.extension.plugins.pagination.Page page = getPage();
IPage<PostDocument> pageResult = searchService.query(page, q);
req.setAttribute("pageData", pageResult);
req.setAttribute("q", q);
return "search";
}
5.4 搜索逻辑实现
/**
* query搜索查询方法逻辑实现
*
* @param page
* @param q
* @return
*/
@Override
public IPage<PostDocument> query(Page page, String q) {
// 分页信息 mybatis plus的page 转成 jpa的page
Long current = page.getCurrent() - 1;
Long size = page.getSize();
Pageable pageable = PageRequest.of(current.intValue(), size.intValue());
//多个字段匹配只要满足一个即可返回结果
MultiMatchQueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(q,
IndexKey.POST_TITLE,
IndexKey.POST_AUTHOR,
IndexKey.POST_CATEGORY,
IndexKey.POST_DESCRIPTION,
IndexKey.POST_TAGS);
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(queryBuilder)
.withPageable(pageable).build();
org.springframework.data.domain.Page<PostDocument> documents = postRepository.search(searchQuery);
IPage pageData = new Page(page.getCurrent(), page.getSize(), documents.getTotalElements());
pageData.setRecords(documents.getContent());
return pageData;
}
5.5 实现效果
输入Layblog,回车搜索:
搜索到1记录返回文章数据: