ES Java Api有很多种,本文主要是基于Spring Boot进行封装的,为什么选择Spring Boot,可以看往期文章 Elasticsearch入门必读指南:到底选择哪个ES版本更合适 。 Spring Boot现在也是Java生态中最主流的开发框架,综上没有理由不使用,所以本文及提供的starter也以此为基础。
一、背景
以往业务应用中使用ES很多依赖的是spring-boot-starter-data-elasticsearch,Spring已经对原生的ES Api进行了封装,但使用起来还是不够高效。ES作为一个搜索引擎中间件,一个企业内业很多业务团队都可能会使用到,封装一个通用的ES Starter供业务团队使用非常有必要。 通过该Starter可以降低业务团队接入及使用成本,也方便后续ES的统一升级管理。
二、高效的my-elasticsearch-starter
my-elasticsearch-starter基于spring-boot-starter-data-elasticsearch封装,也遵循了Spring Boot Starter规范,基于Spring Boot的应用都可以快速引入使用,新的starter提供了更加高效简单的API。对于有一点开发经验的来说使用该Starter预估10分钟左右就可以完成ES的接入及基本的Demo的开发。
该Starter在Github上开源,地址如下 :GitHub - caizi12/my-elasticsearch-starter: elasticsearch starter
如果不方便访问的话,也可以下载压缩包
以下介绍整体Starter的使用方式。
2.1 使用方式
使用时可以先把代码Deploy到公司的私有仓库中,然后应用程序中依赖使用,如果没有私有仓库可以把代码copy到应用中使用。
2.1.1、应用添加依赖
<dependency>
<groupId>com.my.es</groupId>
<artifactId>elasticsearch-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
2.1.2、应用添加ES链接配置
以下是properties格式,如果是Yml格式自行调整即可
#es链接地址
spring.elasticsearch.uris=http://localhost:9200
#es账号密码,根据实际填写
spring.elasticsearch.username=elastic
spring.elasticsearch.password=123456
#可省配置:连接es集群超时参数,默认毫秒
spring.elasticsearch.connection-timeout=300
spring.elasticsearch.read-timeout=300
以上两步完成后应用就已经接入了ES,接下来直接开发业务代码即可
3、ES API Demo,高效完成一个学生ES Demo
3.1 定义索引对象
定义一个学生索引对象,方便在代码中开发使用
package com.my.es.test.model;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
/**
*
* 学生索引
* @authro nantian
* @date 2022-09-28 15:34
*/
@Data
@AllArgsConstructor
@Document(indexName = "index_student")
//分片设置,一般不建议代码中创建索引,在Kibana中进行管理比较合适
//@Setting(shards = 5,replicas = 1)
@NoArgsConstructor
public class Student {
//索引主键
@Id
private long id;
@Field(type = FieldType.Keyword)
private String name;
@Field(type = FieldType.Keyword)
private String text;
//@Field(type = FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_smart")
@Field(type = FieldType.Keyword)
private String desc;
//@Field(type = FieldType.Text,analyzer = "ik_smart")
@Field(type = FieldType.Keyword)
private String title;
@Field(type = FieldType.Integer)
private Integer age;
@Field(type = FieldType.Date)
private Date date;
//版本号,可以解决数据冲突问题
@Version
private Long version;
}
3.2 增删改查
@SpringBootTest
public class MyEsServiceTest {
@Autowired
private MyEsService myEsService;
//删除索引
@Test
public void delIndex() {
boolean result = myEsService.deleteIndexIfExist(Student.class);
Assert.assertTrue(result);
}
//更新索引结构
@Test
public void updateMapping() {
boolean result = myEsService.updateIndexMapping(Student.class);
Assert.assertTrue(result);
}
//创建学生索引
@Test
public void createIndex() {
boolean exist = myEsService.existIndex(Student.class);
boolean result = false;
if (!exist) {
result = myEsService.createIndexIfNotExist(Student.class);
} else {
System.out.println("index exist:" + Student.class.getName());
}
Assert.assertTrue(result);
}
//添加一个学生数据到索引中
@Test
public void addIndexDoc() {
Student student = new Student(1000, "张三", "测试索引添加", "哈哈", "三年二班刘", 10, new Date(), null);
String documentId = myEsService.addIndexDoc(student);
System.out.println("addIndexDoc result:" + documentId);
Assert.assertNotNull(documentId);
}
//更新一个学生数据
@Test
public void updateDoc() throws JsonProcessingException {
Student student = new Student();
student.setId(1000);
student.setAge(30);
student.setText("lisi");
UpdateResponse.Result result = myEsService.updateDoc(student);
System.out.println("update result:" + JSONObject.toJSONString(result));
}
//删除一个学生数据
@Test
public void delIndexDoc() {
String result = myEsService.delIndexDoc("3007", Student.class);
System.out.println("delIndexDoc:" + Student.class.getName());
}
//根据学生ID查询
@Test
public void getByIdStudent() {
Student student = myEsService.findById("1000", Student.class);
System.out.println(JSONObject.toJSONString(student));
}
}
以上展示的一是一些基本用法,在源码中还有更多的用法,比如批量保存、更新、删除、查询等操作。整体使用起来非常简单,和使用Mybatis是不是感觉很像。
3.3 Starter中提供的API
封装的API中提供了非常丰富的特性足以满足大部分场景的使用了
/**
* es服务接口,该接口提供对es的增删改查操作
*
* @authro nantian
* @date 2022-10-08 15:19
*/
public interface MyEsService {
/**
* 判断索引是否存在, 文档需标注@Document注解
*
* @param clazz
* @return
*/
boolean existIndex(Class<?> clazz);
/**
* 判断索引是否存在, 文档需标注@Document注解
*
* @param clazz
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
boolean existIndex(Class<?> clazz, boolean nonTenantMode);
/**
* 创建索引并设置mapping,setting信息
* 文档需标注@Document注解、包含@Id注解,其它属性字段需要添加@Field注解
*
* @param clazz
* @return
*/
boolean createIndexIfNotExist(Class<?> clazz);
/**
* 创建索引并设置mapping,setting信息
* 文档需标注@Document注解、包含@Id注解,其它属性字段需要添加@Field注解
*
* @param clazz
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
boolean createIndexIfNotExist(Class<?> clazz, boolean nonTenantMode);
/**
* 更新索引mapping信息,已存在的索引重复调用新加的字段会自动更新上去,老字段不会变化
*
* @param clazz
* @return
*/
boolean updateIndexMapping(Class<?> clazz);
/**
* 更新索引mapping信息,已存在的索引重复调用新加的字段会自动更新上去,老字段不会变化
*
* @param clazz
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
boolean updateIndexMapping(Class<?> clazz, boolean nonTenantMode);
/**
* 删除索引,业务应用中不建议用,如果有必要联系管理员在Kibana控台进行操作
*
* @param clazz
* @return
*/
boolean deleteIndexIfExist(Class<?> clazz);
/**
* 删除索引,业务应用中不建议用,如果有必要联系管理员在Kibana控台进行操作
*
* @param clazz
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
boolean deleteIndexIfExist(Class<?> clazz, boolean nonTenantMode);
/**
* 判断一个文档是否存在
*
* @param clazz
* @param docId
* @return
*/
boolean existDocById(Class<?> clazz, String docId);
/**
* 判断一个文档是否存在
*
* @param clazz
* @param docId
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
boolean existDocById(Class<?> clazz, String docId, boolean nonTenantMode);
/**
* 添加一个数据到索引中,推荐使用@addIndexDoc(T model)
*
* @param indexName 索引名
* @param model 索引数据,注解@Id的字段值不允许为空
* @return 文档ID
*/
<T> String addIndexDoc(String indexName, T model);
/**
* 添加一个数据到索引中,推荐使用@addIndexDoc(T model)
*
* @param indexName 索引名
* @param model 索引数据,注解@Id的字段值不允许为空
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return 文档ID
*/
<T> String addIndexDoc(String indexName, T model, boolean nonTenantMode);
/**
* 添加一个数据到索引中
* 会自动获取类上的@Document(indexName)属性当索引名
*
* @param model 文档数据,注解@Id的字段值不允许为空
* @return
*/
<T> String addIndexDoc(T model);
/**
* 添加一个数据到索引中
* 会自动获取类上的@Document(indexName)属性当索引名
*
* @param model 文档数据,注解@Id的字段值不允许为空
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> String addIndexDoc(T model, boolean nonTenantMode);
/**
* 添加一个数据到索引中,指定数据版本号
*
* @param model es文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @param version 数据版本号
* @return
*/
<T> String saveIndexDoc(T model, Long version);
/**
* 添加一个数据到索引中,指定数据版本号
*
* @param model es文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @param version 数据版本号
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> String saveIndexDoc(T model, Long version, boolean nonTenantMode);
/**
* 添加一个数据到索引中
* 会自动获取类上的@Document(indexName)属性当索引名
* 指定数据版本号
*
* @param indexName 索引名称
* @param model es文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @param version 数据版本号
* @return
*/
<T> String saveIndexDoc(String indexName, T model, Long version);
/**
* 添加一个数据到索引中
* 会自动获取类上的@Document(indexName)属性当索引名
* 指定数据版本号
*
* @param indexName 索引名称
* @param model es文档; 文档需标注@Document注解、包含@Id注解字段, 且@Id注解标注的文档ID字段值不能为空
* @param version 数据版本号
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> String saveIndexDoc(String indexName, T model, Long version, boolean nonTenantMode);
/**
* 批量添加索引,推荐使用@bulkAddIndexDoc(Class<?> clazz, List<T> docList)
*
* @param indexName
* @param docList
* @return
*/
<T> List<IndexedObjectInformation> bulkAddIndexDoc(String indexName, List<T> docList);
/**
* 批量添加索引,推荐使用@bulkAddIndexDoc(Class<?> clazz, List<T> docList)
*
* @param indexName
* @param docList
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> List<IndexedObjectInformation> bulkAddIndexDoc(String indexName, List<T> docList, boolean nonTenantMode);
/**
* 批量添加索引
*
* @param indexName 索引名称
* @param docList es文档集合; 文档需标注@Document注解、包含@Id、@Version注解字段, 且@Id注解标注的文档ID字段值不能为空、@Version注解标注的文档数据版本字段值不能为空
* @return
*/
<T> List<IndexedObjectInformation> bulkSaveIndexDoc(String indexName, List<T> docList);
/**
* 批量添加索引
*
* @param indexName 索引名称
* @param docList es文档集合; 文档需标注@Document注解、包含@Id、@Version注解字段, 且@Id注解标注的文档ID字段值不能为空、@Version注解标注的文档数据版本字段值不能为空
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> List<IndexedObjectInformation> bulkSaveIndexDoc(String indexName, List<T> docList, boolean nonTenantMode);
/**
* 批量添加索引,会自动获取类上的 @Document(indexName)属性当索引名
*
* @param clazz
* @param docList
* @return
*/
<T> List<IndexedObjectInformation> bulkAddIndexDoc(Class<?> clazz, List<T> docList);
/**
* 批量添加索引,会自动获取类上的 @Document(indexName)属性当索引名
*
* @param clazz
* @param docList
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> List<IndexedObjectInformation> bulkAddIndexDoc(Class<?> clazz, List<T> docList, boolean nonTenantMode);
/**
* 批量添加索引
*
* @param clazz 会自动获取类上的 @Document(indexName)属性当索引名
* @param docList es文档集合; 文档需标注@Document注解、包含@Id、@Version注解字段, 且@Id注解标注的文档ID字段值不能为空、@Version注解标注的文档数据版本字段值不能为空
* @return
*/
<T> List<IndexedObjectInformation> bulkSaveIndexDoc(Class<?> clazz, List<T> docList);
/**
* 批量添加索引
*
* @param clazz 会自动获取类上的 @Document(indexName)属性当索引名
* @param docList es文档集合; 文档需标注@Document注解、包含@Id、@Version注解字段, 且@Id注解标注的文档ID字段值不能为空、@Version注解标注的文档数据版本字段值不能为空
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> List<IndexedObjectInformation> bulkSaveIndexDoc(Class<?> clazz, List<T> docList, boolean nonTenantMode);
/**
* 更新文档,会自动获取类上的@Document(indexName)属性当索引名
*
* @param model 注解@Id的字段值不允许为空
* @return
*/
<T> UpdateResponse.Result updateDoc(T model);
/**
* 更新文档,会自动获取类上的@Document(indexName)属性当索引名
*
* @param model 注解@Id的字段值不允许为空
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> UpdateResponse.Result updateDoc(T model, boolean nonTenantMode);
/**
* 批量更新文档,会自动获取类上的@Document(indexName)属性当索引名
*
* @param clazz
* @param <T> 注解@Id的字段值不允许为空
* @return
*/
<T> List<IndexedObjectInformation> bulkUpdateDoc(Class<?> clazz, List<T> modelList);
/**
* 批量更新文档
*
* @param clazz
* @param <T> 注解@Id的字段值不允许为空
* @param bulkOptions
* @return
*/
<T> List<IndexedObjectInformation> bulkUpdateDoc(Class<?> clazz, List<T> modelList, BulkOptions bulkOptions);
/**
* 根据ID删除一个索引文档
*
* @param id
* @param clazz
* @return
*/
String delIndexDoc(String id, Class<?> clazz);
/**
* 根据ID删除一个索引文档
*
* @param id
* @param clazz
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
String delIndexDoc(String id, Class<?> clazz, boolean nonTenantMode);
/**
* 批量删除索引
*
* @param clazz
* @param ids
* @return
*/
List<String> bulkDelIndexDoc(Class<?> clazz, List<String> ids);
/**
* 批量删除索引
*
* @param clazz
* @param ids
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
List<String> bulkDelIndexDoc(Class<?> clazz, List<String> ids, boolean nonTenantMode);
/**
* 删除一个索引文档,会自动从类上获取注解为@Id属性的value当作ID
*
* @param model
* @param <T>
* @return
*/
<T> String delIndexDoc(T model);
/**
* 删除一个索引文档,会自动从类上获取注解为@Id属性的value当作ID
*
* @param model
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> String delIndexDoc(T model, boolean nonTenantMode);
/**
* @param docId
* @param tClass
* @param <T>
* @return
*/
<T> T findById(String docId, Class<T> tClass);
/**
* @param docId
* @param clazz
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @param <T>
* @return
*/
<T> T findById(String docId, Class<T> clazz, boolean nonTenantMode);
/**
* 根据ID批量查询
*
* 使用id查询数据实时性更好
*
* @param indexName
* @param clazz
* @param docIdList
* @param nonTenantMode
* @param <T>
* @return
*/
<T> List<T> findByIds(String indexName, Class<T> clazz, List<String> docIdList, boolean nonTenantMode) ;
<T> List<T> findByIds(Class<T> clazz, List<String> docIdList) ;
<T> List<T> findByIds(Class<T> clazz, List<String> docIdList,boolean nonTenantMode) ;
/**
* 更丰富灵活的索引查询,开放spring-boot-es-starter原生NativeSearchQueryBuilder
*
* @param clazz 自动获取类上的@Document(indexName)属性当索引名
* @param queryBuilder
* @return
*/
<T> SearchHits<T> search(Class<T> clazz, NativeSearchQueryBuilder queryBuilder);
/**
* 更丰富灵活的索引查询,开放spring-boot-es-starter原生NativeSearchQueryBuilder
*
* @param clazz 自动获取类上的@Document(indexName)属性当索引名
* @param queryBuilder
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> SearchHits<T> search(Class<T> clazz, NativeSearchQueryBuilder queryBuilder, boolean nonTenantMode);
/**
* 封装查询对象,简化NativeSearchQueryBuilder构造过程
*
* @param clazz 自动获取类上的@Document(indexName)属性当索引名
* @param request
* @return
*/
<T> SearchHits<T> search(Class<T> clazz, MyEsSearchRequest request);
/**
* 封装查询对象,简化NativeSearchQueryBuilder构造过程
*
* @param clazz 自动获取类上的@Document(indexName)属性当索引名
* @param request
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> SearchHits<T> search(Class<T> clazz, MyEsSearchRequest request, boolean nonTenantMode);
/**
* 精确查询类场景推荐使用,es不会计算文档相关性分值,性能更好
*
* @param clazz 自动获取类上的@Document(indexName)属性当索引名
* @param filterBuilder
* @param pageable
* @return
*/
<T> SearchHits<T> searchByFilter(Class<T> clazz, QueryBuilder filterBuilder, @Nullable Pageable pageable);
/**
* 精确查询类场景推荐使用,es不会计算文档相关性分值,性能更好
*
* @param clazz 自动获取类上的@Document(indexName)属性当索引名
* @param filterBuilder
* @param pageable
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> SearchHits<T> searchByFilter(Class<T> clazz, QueryBuilder filterBuilder, @Nullable Pageable pageable, boolean nonTenantMode);
/**
* 标题或文章内容检索类场景推荐使用,es会计算文档相关性,并按相关性自动排序
*
* @param clazz
* @param queryBuilder
* @param pageable
* @return
*/
<T> SearchHits<T> search(Class<T> clazz, QueryBuilder queryBuilder, @Nullable Pageable pageable);
/**
* 标题或文章内容检索类场景推荐使用,es会计算文档相关性,并按相关性自动排序
*
* @param clazz
* @param queryBuilder
* @param pageable
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> SearchHits<T> search(Class<T> clazz, QueryBuilder queryBuilder, @Nullable Pageable pageable, boolean nonTenantMode);
/**
* 索引数据查询
*
* @param clazz 索引类
* @param queryBuilder
* @param filterBuilder
* @return
*/
<T> SearchHits<T> search(Class<T> clazz, QueryBuilder queryBuilder, QueryBuilder filterBuilder, @Nullable Pageable pageable);
/**
* 索引数据查询
*
* @param clazz 索引类
* @param queryBuilder
* @param filterBuilder
* @param nonTenantMode 是否是租户模式,true表示非租户模式,即通用索引
* @return
*/
<T> SearchHits<T> search(Class<T> clazz, QueryBuilder queryBuilder, QueryBuilder filterBuilder, @Nullable Pageable pageable, boolean nonTenantMode);
}
整个Starter具体实现原理大家可以看看代码,还算是比较简单,部分API主要用到了一些反射机制,浪费一点性能带来高效的开发体验还是很值得的,有兴趣的可以留言交流。