一、Elasticsearch 介绍
Elaticsearch简称为ES,是一个开源的可扩展的分布式全文检索引擎服务器,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。ES使用Java开发并使用Lucene作为其核心来实现索引和搜索的功能,它通过简单的RestfulAPI和JavaAPI来隐藏Lucene的复杂性,从而让全文搜索变得简单。
Elasticsearch官网:https://www.elastic.co/cn/products/
1.1 使用场景
- 分布式的搜索引擎
- 分布式:Elasticsearch自动将海量数据分散到多台服务器上去存储和检索
- 搜索:百度、谷歌,站内搜索
- 全文检索
- 提供模糊搜索等自动度很高的查询方式,并进行相关性排名,高亮等功能
- 数据分析引擎(分组聚合)
- 电商网站,最近一周笔记本电脑这种商品销量排名top10的商家有哪些?新闻网站,最近1个月访问量排名top3的新闻板块是哪些
- 对海量数据进行近实时的处理
- 海量数据的处理:因为是分布式架构,Elasticsearch可以采用大量的服务器去存储和检索数据,自然而然就可以实现海量数据的处理 近实时:Elasticsearch可以实现秒级别的数据搜索和分析
1.2 相关概念
索引库[index]-----------------------------------Database 数据库
类型[type]----------------------------------Table 数据表
文档[Document]--------------------------Row 行
字段[Field]-------------------------Column 列
映射[mapping]-------------------表结构
详细说明
| 概念 | 说明 |
|---|---|
| 索引 | 索引包含一堆相关业务,结构相似的文档数据,比如说建立一个商品product索引库,里面就存放了所有的商品数据。 |
| 类型 | 类型是索引库中的一个逻辑数据分类,一个类型下的文档,都有相同的字段,类似于数据库中的表。比如商品类型,里面存放了所有的商品文档数据。6.0版本以前每个索引里可以是一个或多个类型,6.0版本以后一个索引只能有1个类型,7.x以后已经移除了这个概念。 |
| 文档 | 文档是存入索引库最小数据单元,一个文档可以是一条客户数据,一条商品数据,一条订单数据,通常用Json数据结构表示。文档存在索引库下的类型中。 |
| 字段 | Field是Elasticsearch的最小单位。一个document里面有多个field,每个field就是一个数据字段。 |
| 映射 | 类型对文档结构的约束叫做映射(mapping),用来定义document的每个字段的约束。如:字段的数据类型、是否分词、是否索引、是否存储等特性。类型是模拟mysql中的table概念。表是有结构的,也就是表中每个字段都有约束信息。 |
最新版ES中的 index、document、filed、mapping这样的概念,对比参考MySQL如下:
| ElasticSearch | MySQL |
|---|---|
| index | 表 |
| document | 行 |
| field | 列 |
| mapping | 表结构 |
1.3 原理举例
要实现全文搜索的效果,不可能使用数据库中like操作去进行比对,这种效率太低了。ES设计了一种全新的思想,来实现全文搜索。具体操作过程如下:
- 将被查询的字段的数据全部文本信息进行拆分,分成若干个词
- 例如“北京科技公司”就会被拆分成三个词,分别是“北京”、“科技”、“公司”,此过程有专业术语叫做分词。分词的策略不同,分出的效果不一样,不同的分词策略称为分词器。
- 将分词得到的结果存储起来,对应每条数据的id
- 例如id为1的数据中名称这一项的值是“北京科技公司”,那么分词结束后,就会出现“北京”对应id为1,“科技”对应id为1,“公司”对应id为1。
- 例如id为2的数据中名称这一项的值是“北京朝阳群众“,那么分词结束后,就会出现“北京”对应id为2,“朝阳”对应id为2,“群众”对应id为2。
- 此时就会出现如下对应结果,按照上述形式可以对所有文档进行分词。需要注意分词的过程不是仅对一个字段进行,而是对每一个参与查询的字段都执行,最终结果汇总到一个表格中。
| 分词结果关键字 | 对应id |
|---|---|
| 北京 | 1,2 |
| 科技 | 1 |
| 公司 | 1 |
| 朝阳 | 2 |
| 群众 | 2 |
- 当进行查询时,如果输入“北京”作为查询条件,可以通过上述表格数据进行比对,得到id值1,2,然后根据id值就可以得到查询的结果数据了。
二、Java操作示例
2.1 示例源码
- SpringBoot2
- SpringBoot3
PS:本文只是一篇极其简单的整合教程,不涉及复杂搜索示例,建议认真阅读官方文档
2.2 版本对应关系
| Spring Boot | Spring Data Elasticsearch | Elasticsearch |
|---|---|---|
| 2.4.x | 4.1.x | 7.9.x |
| 2.5.x | 4.2.x | 7.12.x |
| 2.6.x | 4.3.x | 7.15.x |
| 2.7.x | 4.4.x | 7.17.x |
- 因为
Spring Boot 2.7.18对应的Spring Data Elasticsearch 4.4.18对应的Elasticsearch的版本为7.17.18,所以本文以此版本为例- Spring Boot 2.7.18
- Spring Data Elasticsearch 4.4.18
- Elasticsearch 7.17.18
- 本文不介绍
Reactive模式的相关代码
2.3 引入Maven依赖包
pom.xml
<!--++++++++++++++++++++++++++++++++++++++++++++++++++++-->
<!-- Elasticsearch搜索引擎 -->
<!--++++++++++++++++++++++++++++++++++++++++++++++++++++-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
2.4 客户端配置
注意:官网未介绍yml方式配置,但是也是可以使用的,不做复杂的设置的话,yml即可
方式一:application.yml
# yml配置Elasticsearch客户端地址(可配置项有限)
spring:
elasticsearch:
uris: http://127.0.0.1:9200 # Elasticsearch 连接地址
#username: elastic # 用户名
#password: 123456 # 密码
connection-timeout: 10s # 连接超时时间(默认1s)
socket-timeout: 30s # 数据读取超时时间(默认30s)
方式二:Bean
import java.time.Duration;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.RestClients;
import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;
/**
* Java高级别REST客户端是Elasticsearch的默认客户端
*
* Bean方式配置
*/
@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
@Bean
@Override
public RestHighLevelClient elasticsearchClient() {
// 使用构建器来提供集群地址,设置默认值HttpHeaders或启用SSL。
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
// 设置连接地址
.connectedTo("127.0.0.1:9200")
// 可以设置多个地址
// .connectedTo("127.0.0.1:9200", "127.0.0.1:9201")
// 是否启用ssl
// .usingSsl()
// 设置连接超时时间
.withConnectTimeout(Duration.ofSeconds(10))
// 设置
.withSocketTimeout(Duration.ofSeconds(30))
// 设置用户名密码
// .withBasicAuth("elastic", "123456")
// 创建连接信息
.build();
// 创建RestHighLevelClient。
return RestClients.create(clientConfiguration).rest();
}
}
2.5 日志输出
方便查询发送的http请求详细内容
在application.yml内添加如下内容:
logging:
file:
name: ./log/log.log
level:
root: info
org.springframework.data.elasticsearch.client.WIRE: trace
2.6 实体映射
官方文档:Elasticsearch Object Mapping
package com.wanma.apps.model.bo;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.time.LocalDateTime;
@Document(indexName = "user", shards = 3, replicas = 0)
public class User {
@Id
private Integer id;
@Field(type = FieldType.Keyword)
private String name;
@Field(type = FieldType.Integer)
private Integer age;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String address;
@Field(type = FieldType.Date, format = DateFormat.custom, pattern = "uuuu-MM-dd'T'HH:mm:ss.SSSX")
private LocalDateTime createTime;
}
注解说明:
在MappingElasticsearchConverter使用元数据驱动的对象的映射文件。元数据取自可以注释的实体属性。
提供以下注释:
@Document:在类级别应用,以指示该类是映射到数据库的候选对象。最重要的属性是:
indexName:用于存储此实体的索引的名称。它可以包含SpEL模板表达式,例如 "log-#{T(java.time.LocalDate).now().toString()}"
createIndex:标记是否在存储库引导中创建索引。默认值为true。请参见使用相应的映射自动创建索引
versionType:版本管理的配置。默认值为EXTERNAL。
@Id:在字段级别应用,以标记用于标识目的的字段。
@Transient:默认情况下,存储或检索文档时,所有字段都映射到文档,此注释不包括该字段。
@PersistenceConstructor:标记从数据库实例化对象时要使用的给定构造函数,甚至是受保护的程序包。构造函数参数按名称映射到检索到的Document中的键值。
@Field:在字段级别应用并定义字段的属性,大多数属性映射到各自的Elasticsearch映射定义(以下列表不完整,请查看注释Javadoc以获得完整参考):
name:字段名称,它将在Elasticsearch文档中表示,如果未设置,则使用Java字段名称。
type:字段类型,可以是Text, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float, Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type。请参阅Elasticsearch映射类型
format和日期类型的pattern定义。必须为日期类型定义format
store:标记是否将原始字段值存储在Elasticsearch中,默认值为false。
analyzer,searchAnalyzer,normalizer用于指定自定义分析和正规化。
@GeoPoint:将字段标记为geo_point数据类型。如果字段是GeoPoint类的实例,则可以省略。
@ValueConverter:定义用于转换给定属性的类。与注册的 Spring 不同,Converter这仅转换带注释的属性,而不是给定类型的每个属性。
@Setting:注释定义不同的索引设置。以下参数可用:
useServerConfiguration 不发送任何设置参数,因此 Elasticsearch 服务器配置确定它们。
settingPath 是指定义必须在类路径中解析的设置的 JSON 文件
shards要使用的分片数,默认为1
replicas副本数,默认为1
refreshIntervall, 默认为“1s”
indexStoreType, 默认为"fs"
2.7 增、删、改、查
Elasticsearch Operations
主要有以下4个类
IndexOperations在索引级别定义操作,例如创建或删除索引。DocumentOperations定义根据其ID存储,更新和检索实体的操作。SearchOperations定义使用查询搜索多个实体的动作ElasticsearchOperations结合DocumentOperations和SearchOperations的接口ElasticsearchRestTemplate是ElasticsearchOperations的实现类
日常使用时,使用
ElasticsearchRestTemplate即可,代码如下
@Autowired private ElasticsearchRestTemplate template;
IndexOperations需要从ElasticsearchRestTemplate中获取,例如:template.indexOps(User.class)
(1)操作索引
// 获取IndexOperations对象 IndexOperations indexOperations = template.indexOps(User.class); // 查 boolean exists = indexOperations.exists(); // 删 boolean delete = indexOperations.delete(); // 增 boolean flag = indexOperations.create(); // 设置Mapping boolean mapping = indexOperations.putMapping();
完整示例:com.maxqiu.demo.IndexApi
(2)操作文档
// 增/改
User user = new User();
user.set...
template.save(user);
// 批量 增/改
List<User> userList = new ArrayList<>();
userList.add(new User);
...
Iterable<User> users = template.save(userList);
// 查
User user = template.get("1", User.class);
// 删
String delete = template.delete("1", User.class);
完整示例:com.maxqiu.demo.DocumentApi
(3)搜索
// 新建一个QueryBuilder(该对象是Elasticsearch的,QueryBuilders可以构建各种各样的条件查询) QueryBuilder queryBuilder = QueryBuilders.matchAllQuery(); // 新建一个Query(该对象是Spring Data Elasticsearch的) Query query = new NativeSearchQueryBuilder().withQuery(queryBuilder).build(); // 使用template执行查询,SearchHits<User>就是执行后的返回结果的映射实体 SearchHits<User> search = template.search(query, User.class);
完整示例:com.maxqiu.demo.SearchApi
ElasticsearchRepository
官方文档:Elasticsearch Repositories
一个类似于Spring Data JPA的文档操作方法
// 新建一个User的数据持久层
@Repository
public interface UserRepository extends ElasticsearchRepository<User, Integer> {}
建立此持久层后,如果User实体@Document注解内的createIndex为true时(默认为true),则服务启动时会先检查索引是否存在,若不存在则会自动创建索引。
(1)默认的CRUD示例
// 增/改 User user = new User(1, "张三", 18, "上海市闵行区", LocalDateTime.now()); User save = repository.save(user); // 查 Optional<User> optionalUser = repository.findById(1); // 删 repository.deleteById(1);
完整示例:com.maxqiu.demo.repository.UserRepositoryTest
(2)自定义的条件查询
在UserRepository内新建抽象方法
List<User> findUsersByNameAndAddress(String name, String address);
调用
@Test
void test() {
List<User> userList = repository.findUsersByNameAndAddress("张三", "上海");
for (User user : userList) {
System.out.println(user);
}
}
参考: