搜索引擎ORM框架Easy-Es(搜索引擎 框架)
项目简介
Easy-Es(简称EE) 是一款基于ElasticSearch(简称Es)官方提供的RestHighLevelClient打造的ORM开发框架,在RestHighLevelClient的基础上,只做增强不做改变,为简化开发、提高效率而生,您如果有用过MyBatis-Plus(简称MP),那么您基本可以零学习成本直接上手EE,EE是MP的Es平替版,在有些方面甚至比MP更简单,同时也融入了更多Es独有的功能,助力您快速实现各种场景的开发。
项目地址
- Easy-Es Gitee:gitee.com/dromara/eas…
- Elasticsearch Download:www.elastic.co/cn/download…
Easy-Es自身优势
- 全自动索引托管: 全球开源首创的索引托管模式,开发者无需关心索引的创建更新及数据迁移等繁琐步骤,索引全生命周期皆可托管给框架,由框架自动完成,过程零停机,用户无感知,彻底解放开发者;
- 屏蔽语言差异: 开发者只需要会MySQL语法即可使用Es,真正做到一通百通,无需学习枯燥易忘的Es语法,Es使用相对MySQL较低频,学了长期不用也会忘,没必要浪费这时间.开发就应该专注于业务,省下的时间去撸铁,去陪女朋友陪家人,不做资本家的韭菜;
- 代码量极少: 与直接使用RestHighLevelClient相比,相同的查询平均可以节省3-5倍左右的代码量
- 零魔法值: 字段名称直接从实体中获取,无需输入字段名称字符串这种魔法值,提高代码可读性,杜绝因字段名称修改而代码漏改带来的Bug;
- 零额外学习成本: 开发者只要会国内最受欢迎的Mybatis-Plus语法,即可无缝迁移至EE,EE采用和前者相同的语法,消除使用者额外学习成本,直接上手;
- 降低开发者门槛: Es通常需要中高级开发者才能驾驭,但通过接入EE,即便是只了解ES基础的初学者也可以轻松驾驭ES完成绝大多数需求的开发,可以提高人员利用率,降低企业成本。
特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑;
- 损耗小:启动即会自动注入基本CURD,性能基本无损耗,直接面向对象操作;
- 强大的 CRUD 操作:内置通用Mapper,仅仅通过少量配置即可实现大部分CRUD操作,更有强大的条件构造器,满足各类使用需;
- 支持 Lambda 形式调用:通过Lambda表达式,方便的编写各类查询条件,无需再担心字段写错段;
- 支持主键自动生成:支持 2 种主键策略,可自由配置,完美解决主键问题;
- 支持 ActiveRecord 模式:支持ActiveRecord形式调用,实体类只需继承Model类即可进行强大的 CRUD操作;
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere );
- 内置分页插件:基于RestHighLevelClient物理分页,开发者无需关心具体操作,且无需额外配置插件,写分页等同于普通List查询,且保持和PageHelper插件同样的分页返回字段,无需担心命名影响;
- MySQL功能全覆盖:MySQL中支持的功能通过EE都可以轻松实现;
- 支持ES高阶语法:支持高亮搜索,分词查询,权重查询, Geo地理位置查询、IP查询、聚合查询等高阶语法;
- 良好的拓展性:底层仍使用RestHighLevelClient,可保持其拓展性,开发者在使用EE的同时,仍可使用RestHighLevelClient的功能。
✨最新版本 Latest Version: v.1.1.1
Maven:
xml复制代码<dependency> <groupId>cn.easy-es</groupId> <artifactId>easy-es-boot-starter</artifactId> <version>${Latest Version}</version></dependency>
Gradle:
csharp复制代码compile group: 'cn.easy-es', name: 'easy-es-boot-starter', version: 'Latest Version'
Easy-Es适用场景
1️⃣ 检索类服务
- 搜索文库
- 电商商品搜索
- 海量系统日志搜索
2️⃣ 问答类服务
- 在线智能客服
- 机器人
3️⃣ 地图类服务
- 打车APP
- 外卖APP
- 社区团购配送
- 社交APP
注解
EsMapperScan
- 描述:mapper扫描注解,功能与Mybatis Plus的@MapperScan一致
- 使用位置:Spring Boot启动类
kotlin复制代码@EsMapperScan("cn.easy-es-mapper")public class Application{ // 省略其它...}
@IndexName
- 描述:索引名注解,标识实体类对应的索引 对应Mybatis Plus的@TableName注解,在v0.9.40之前此注解为@TableName;
- 使用位置:实体类
kotlin复制代码@IndexNamepublic class Document { // 省略其它字段}
属性 | 类型 | 是否必须指定 | 默认值 | 描述 |
value | String | 否 | "" | 索引名,可以简单理解为MySQL表名 |
shardsNum | int | 否 | 1 | 索引分片数 |
replicasNum | int | 否 | 1 | 索引副本数 |
aliasName | String | 否 | "" | 索引别名 |
keepGlobalPrefix | Boolean | 否 | false | 是否保持使用全局的tablePrefix,与MP的用法一致 |
child | boolean | 否 | false | 是否子文档 |
childClass | Class | 否 | DefaultChildClass.class | 父子分档-子文档类 |
maxResultWindow | int | 否 | 10000 | 分页返回的最大数据量,默认值为1万条,超出推荐使用searchAfter或滚动查询等方式 |
@IndexId
- 描述:Easy-Es主键注解,在v0.9.40之前此注解为@TableId;
- 使用位置:实体类中被作为Easy-Es主键的字段, 对应MyBatis-Plus的@TableId注解。
kotlin复制代码public class Document { @IndexId private String id; // 省略其它字段}
属性 | 类型 | 必须指定 | 默认值 | 描述 |
value | String | 否 | "_id" | 主键字段名 |
type | Enum | 否 | IdType.NONE | 指定主键类型 |
@IndexField
- 描述:ES字段注解, 对应MP的@TableField注解,在v0.9.40之前此注解为@TableField;
- 使用位置:实体类中被作为ES索引字段的字段;
- 使用场景举例:
- 实体类中的字段并非ES中实际的字段,比如把实体类直接当DTO用了,加了一些ES中并不存在的无关字段,此时可以标记此字段,以便让EE框架跳过此字段,对此字段不处理;
- 字段的更新策略,比如在调用更新接口时,实体类的字段非Null或者非空字符串时才更新,此时可以加字段注解,对指定字段标记更新策略;
- 需要对类型为text或keyword_text字段聚合时,可指定其fieldData=true,否则es会报错;
- 对指定字段进行自定义命名,比如该字段在es中叫wu-la,但在实体model中叫ula,此时可以在value中指定value="wu-la";
- 在自动托管索引模式下,可指定索引分词器及索引字段类型;
- 在自动托管索引模式下,可指定索引中日期的format格式。
使用示例:
typescript复制代码public class Document { // 此处省略其它字段... // 场景一:标记es中不存在的字段 @IndexField(exist = false) private String notExistsField; // 场景二:更新时,此字段非空字符串才会被更新 @IndexField(strategy = FieldStrategy.NOT_EMPTY) private String creator; // 场景三: 指定fieldData @IndexField(fieldType = FieldType.text, fieldData = true) private String filedData; // 场景四:自定义字段名 @IndexField("wu-la") private String ula; // 场景五:支持日期字段在es索引中的format类型 @IndexField(fieldType = FieldType.DATE, dateFormat = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis") private String gmtCreate; // 场景六:支持指定字段在es索引中的分词器类型 @IndexField(fieldType = FieldType.TEXT, analyzer = Analyzer.IK_SMART, searchAnalyzer = Analyzer.IK_MAX_WORD) private String content;}
属性 | 类型 | 必须指定 | 默认值 | 描述 |
value | String | 否 | "" | 字段名 |
exist | boolean | 否 | true | 字段是否存在 |
fieldType | Enum | 否 | FieldType.NONE | 字段在es索引中的类型 |
fieldData | boolean | 否 | false | text类型字段是否支持聚合 |
analyzer | String | 否 | Analyzer.NONE | 索引文档时用的分词器 |
searchAnalyzer | String | 否 | Analyzer.NONE | 查询分词器 |
strategy | Enum | 否 | FieldStrategy.DEFAULT | 字段验证策略 |
dateFormat | String | 否 | "" | es索引中的日期格式,如yyyy-MM-dd |
nestedClass | Class | 否 | DefaultNestedClass.class | 嵌套类 |
parentName | String | 否 | "" | 父子文档-父名称 |
childName | String | 否 | "" | 父子文档-子名称 |
joinFieldClass | Class | 否 | JoinField.class | 父子文档-父子类型关系字段类 |
@Score
- 描述:得分注解
- 使用位置:实体类中被作为ES查询得分返回的字段
- 使用场景举例:比如需要知道本次匹配查询得分有多少时,可以在实体类中添加一个类型为Float/float的字段,并在该字段上添加@Score注解,在后续查询中,若es有返回当次查询的得分,则此得分会自动映射至此字段上
属性 | 类型 | 必须指定 | 默认值 | 描述 |
decimalPlaces | int | 否 | 0 | 得分保留小数位,默认不处理,保持es返回的得分值 |
@Distance
- 描述:距离注解
- 使用位置:实体类中被作为ES地理位置排序距离值的返回字段
- 使用场景举例:比如需要知道按距离由近及远查询后的数据,实际距离某一坐标有多远,可以在实体类中添加一个类型为Double/double的字段,并在该字段上添加@Distance注解,在后续查询中,若es有返回距离,则此距离会自动映射至此字段上
属性 | 类型 | 必须指定 | 默认值 | 描述 |
decimalPlaces | int | 否 | 0 | 距离保留小数位,默认不处理,保持es返回的距离值 |
sortBuilderIndex | int | 否 | 0 | 排序字段在sortBuilders中的位置, 默认为0,若有多个排序器,则指定为其所在位置 |
ES版本和Spring Boot版本
xml复制代码<properties> <java.version>1.8</java.version> <easy-es.version>1.1.1</easy-es.version> <elasticsearch.version>7.14.0</elasticsearch.version></properties><!-- easy-es --><dependency> <groupId>cn.easy-es</groupId> <artifactId>easy-es-boot-starter</artifactId> <version>${easy-es.version}</version> <exclusions> <exclusion> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> </exclusion> <exclusion> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> </exclusion> </exclusions></dependency><!-- elasticsearch --><dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>${elasticsearch.version}</version></dependency><dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>${elasticsearch.version}</version></dependency>
Spring Boot配置
在 application.yml 配置文件中添加EasyEs必须的相关配置:
yaml复制代码easy-es: enable: true #默认为true,若为false则认为不启用本框架 address : 127.0.0.1:9200 # es的连接地址,必须含端口 若为集群,则可以用逗号隔开 例如:127.0.0.1:9200,127.0.0.2:9200 username: elastic #若无 则可省略此行配置 password: WG7WVmuNMtM4GwNYkyWH #若无 则可省略此行配置
BaseEsMapper CRUD接口
Easy-Es封装了基本的CRUD操作API,通过继承BaseEsMapper<T>赋予相关能力,比如:
csharp复制代码public interface DocumentMapper extends BaseEsMapper<Document> {}
相关操作API
php复制代码public interface BaseEsMapper<T> { /** * 是否存在索引 * * @param indexName 索引名称 * @return 返回是否存在的布尔值 */ Boolean existsIndex(String indexName); /** * 获取当前索引信息 * * @return 当前索引信息 */ GetIndexResponse getIndex(); /** * 获取指定索引信息 * * @param indexName 指定索引名 * @return 指定索引信息 */ GetIndexResponse getIndex(String indexName); /** * 创建索引,根据当前mapper对应实体类信息及其注解配置生成索引信息 * * @return 是否创建成功 */ Boolean createIndex(); /** * 创建索引,根据当前mapper对应实体类信息及其注解配置生成索引信息 可指定索引名进行创建 适用于定时任务按日期创建索引场景 * * @param indexName 指定的索引名,会覆盖注解上指定的索引名 * @return 是否创建成功 */ Boolean createIndex(String indexName); /** * 创建索引 * * @param wrapper 条件 * @return 是否成功 */ Boolean createIndex(LambdaEsIndexWrapper<T> wrapper); /** * 更新索引 * * @param wrapper 条件 * @return 是否成功 */ Boolean updateIndex(LambdaEsIndexWrapper<T> wrapper); /** * 删除指定索引 * * @param indexNames 索引名称数组 * @return 是否成功 */ Boolean deleteIndex(String... indexNames); /** * 标准查询 * * @param wrapper 条件 * @return es标准结果 */ SearchResponse search(LambdaEsQueryWrapper<T> wrapper); /** * 获取SearchSourceBuilder,可用于本框架生成基础查询条件,不支持的高阶语法用户可通过SearchSourceBuilder 进一步封装 * * @param wrapper 条件 * @return 查询参数 */ SearchSourceBuilder getSearchSourceBuilder(LambdaEsQueryWrapper<T> wrapper); /** * es原生查询 * * @param searchrequest 查询请求参数 * @param requestOptions 类型 * @return es原生返回结果 * @throws IOException IO异常 */ SearchResponse search(SearchRequest searchRequest, RequestOptions requestOptions) throws IOException; /** * es原生滚动查询 * * @param searchScrollRequest 查询请求参数 * @param requestOptions 类型 * @return es原生返回结果 * @throws IOException IO异常 */ SearchResponse scroll(SearchScrollRequest searchScrollRequest, RequestOptions requestOptions) throws IOException; /** * 获取通过本框架生成的查询参数,可用于检验本框架生成的查询参数是否正确 * * @param wrapper 条件 * @return 查询JSON格式参数 */ String getSource(LambdaEsQueryWrapper<T> wrapper); /** * 指定返回类型及分页参数 * * @param wrapper 条件 * @param pageNum 当前页 * @param pageSize 每页条数 * @return 指定的返回类型 */ EsPageInfo<T> pageQuery(LambdaEsQueryWrapper<T> wrapper, Integer pageNum, Integer pageSize); /** * searchAfter类型分页 * * @param wrapper 条件 * @param searchAfter 当前页 第一页时为null * @param pageSize 每页条数 * @return 指定的返回类型 */ SAPageInfo<T> searchAfterPage(LambdaEsQueryWrapper<T> wrapper, List<Object> searchAfter, Integer pageSize); /** * 获取总数(智能推断:若wrapper中指定了去重字段则去重,若未指定则不去重 推荐使用) * * @param wrapper 条件 * @return 总数 */ Long selectCount(LambdaEsQueryWrapper<T> wrapper); /** * 无论wrapper中是否指定去重字段,都以用户传入的distinct布尔值作为是否去重的条件 * * @param wrapper 条件 * @param distinct 是否去重 * @return 总数 */ Long selectCount(LambdaEsQueryWrapper<T> wrapper, boolean distinct); /** * 插入一条记录 * * @param entity 插入的数据对象 * @return 成功条数 */ Integer insert(T entity); /** * 插入一条记录,可指定多索引插入 * * @param entity 插入的数据对象 * @param indexNames 指定插入的索引名数组 * @return 总成功条数 */ Integer insert(T entity, String... indexNames); /** * 批量插入 * * @param entityList 插入的数据对象列表 * @return 总成功条数 */ Integer insertBatch(Collection<T> entityList); /** * 批量插入 * * @param entityList 插入的数据对象列表 * @param indexNames 指定插入的索引名数组 * @return 总成功条数 */ Integer insertBatch(Collection<T> entityList, String... indexNames); /** * 根据 ID 删除 * * @param id 主键 * @return 成功条数 */ Integer deleteById(Serializable id); /** * 根据 ID 删除 * * @param id 主键 * @param indexNames 指定删除的索引名数组 * @return 总成功条数 */ Integer deleteById(Serializable id, String... indexNames); /** * 删除(根据ID 批量删除) * * @param idList 主键列表 * @return 总成功条数 */ Integer deleteBatchIds(Collection<? extends Serializable> idList); /** * 删除(根据ID 批量删除) * * @param idList 主键列表 * @param indexNames 指定删除的索引名数组 * @return 总成功条数 */ Integer deleteBatchIds(Collection<? extends Serializable> idList, String... indexNames); /** * 根据 entity 条件,删除记录 * * @param wrapper 条件 * @return 总成功条数 */ Integer delete(LambdaEsQueryWrapper<T> wrapper); /** * 根据 ID 更新 * * @param entity 更新对象 * @return 总成功条数 */ Integer updateById(T entity); /** * 根据 ID 更新 * * @param entity 更新对象 * @param indexNames 指定更新的索引名称数组 * @return 总成功条数 */ Integer updateById(T entity, String... indexNames); /** * 根据ID 批量更新 * * @param entityList 更新对象列表 * @return 总成功条数 */ Integer updateBatchByIds(Collection<T> entityList); /** * 根据ID 批量更新 * * @param entityList 更新对象列表 * @param indexNames 指定更新的索引名称数组 * @return 总成功条数 */ Integer updateBatchByIds(Collection<T> entityList, String... indexNames); /** * 根据 whereEntity 条件,更新记录 * * @param entity 更新对象 * @param updateWrapper 条件 * @return 成功条数 */ Integer update(T entity, LambdaEsUpdateWrapper<T> updateWrapper); /** * 根据 ID 查询 * * @param id 主键 * @return 指定的返回对象 */ T selectById(Serializable id); /** * 根据 ID 查询 * * @param id 主键 * @param indexNames 指定查询的索引名数组 * @return 指定的返回对象 */ T selectById(Serializable id, String... indexNames); /** * 查询(根据ID 批量查询) * * @param idList 主键列表 * @return 指定的返回对象列表 */ List<T> selectBatchIds(Collection<? extends Serializable> idList); /** * 查询(根据ID 批量查询) * * @param idList 主键列表 * @param indexNames 指定查询的索引名数组 * @return 指定的返回对象列表 */ List<T> selectBatchIds(Collection<? extends Serializable> idList, String... indexNames); /** * 根据 entity 条件,查询一条记录 * * @param wrapper 条件 * @return 指定的返回对象 */ T selectOne(LambdaEsQueryWrapper<T> wrapper); /** * 根据 entity 条件,查询全部记录 * * @param wrapper 条件 * @return 指定的返回对象列表 */ List<T> selectList(LambdaEsQueryWrapper<T> wrapper); /** * 设置当前Mapper默认激活的全局索引名称 务必谨慎操作,设置后全局生效,永驻jvm,除非项目重启 * * @param indexName 索引名称 * @return 是否成功 */ Boolean setCurrentActiveIndex(String indexName);}
Tips提示:
CRUD接口用法基本与MyBatis-Plus一致;
用户需要继承的Mapper为BaseEsMapper,而非BaseMapper;
EE没有提供Service层,而是把MyBatis-Plus中一些Service层的方法直接下沉到Mapper层了,用户用起来会更方便。
Easy-ES整合实践
- 第一步:确保你的环境已经安装ElasticSearch,博主这里的演示版本为7.14.0,运行ElasticSearch;
- 第二步:创建Maven Project,引入Easy-Es依赖;
- 第三步:定义文档类Document,测试Easy-Es的CRUD基本操作。
1️⃣ 运行ElasticSearch
访问:http://localhost:9200/,查看节点信息。
bash复制代码{ "name": "LAPTOP-8G2EVQFP", # 默认节点名称 "cluster_name": "elasticsearch", "cluster_uuid": "nnFJExSMRj-gy09OeHRk5A", "version": { # ES版本 "number": "7.3.2", "build_flavor": "default", "build_type": "zip", "build_hash": "1c1faf1", "build_date": "2019-09-06T14:40:30.409026Z", "build_snapshot": false, "lucene_version": "8.1.0", "minimum_wire_compatibility_version": "6.8.0", "minimum_index_compatibility_version": "6.0.0-beta1" }, "tagline": "You Know, for Search"}
2️⃣ 创建Spring Boot项目,添加依赖
添加POM依赖:
xml复制代码<properties> <java.version>1.8</java.version> <easy-es.version>1.1.1</easy-es.version> <elasticsearch.version>7.14.0</elasticsearch.version> <spring-boot-data-elasticsearch.version>3.0.4</spring-boot-data-elasticsearch.version></properties><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- easy-es --> <dependency> <groupId>cn.easy-es</groupId> <artifactId>easy-es-boot-starter</artifactId> <version>${easy-es.version}</version> <exclusions> <exclusion> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> </exclusion> <exclusion> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> </exclusion> </exclusions> </dependency> <!-- elasticsearch --> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>${elasticsearch.version}</version> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>${elasticsearch.version}</version> </dependency></dependencies>
文档类Document
arduino复制代码@Datapublic class Document { /** * es中的唯一id */ private String id; /** * 文档标题 */ private String title; /** * 文档内容 */ private String content;}
DocumentMapper
csharp复制代码public interface DocumentMapper extends BaseEsMapper<Document> {}
EsDocumentController
typescript复制代码@RestController@RequiredArgsConstructor(onConstructor = @__(@Autowired))public class EsDocumentController { private final DocumentMapper documentMapper; @GetMapping("/insert") public Integer insert() { // 初始化-> 新增数据 Document document = new Document(); document.setId("1"); document.setTitle("austin"); document.setContent("austin-framework-elasticsearch"); return documentMapper.insert(document); } @RequestMapping("/update") public Integer update() { // 更新 Document document = new Document(); document.setId("1"); document.setContent("updated content!"); return documentMapper.updateById(document); } @GetMapping("/search") public List<Document> search() { // 查询出所有标题为austin的文档列表 LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>(); wrapper.eq(Document::getTitle, "austin"); return documentMapper.selectList(wrapper); } @DeleteMapping("/delete") public Integer delete() { // 根据ID删除 LambdaEsQueryWrapper<Document> esQueryWrapper = new LambdaEsQueryWrapper<>(); esQueryWrapper.eq(Document::getId, "1"); return documentMapper.delete(esQueryWrapper); }}
applcation.properties
ini复制代码server.port=26686spring.application.name=framework-elasticsearchspring.datasource.url=jdbc:mysql://127.0.0.1:3306/jeecg-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghaispring.datasource.username=rootspring.datasource.password=rootspring.datasource.driver-class-name=com.mysql.cj.jdbc.Drivereasy-es.address=127.0.0.1:9200easy-es.enable=trueeasy-es.username=elasticeasy-es.password=
3️⃣ 执行CRUD操作
访问:http://localhost:26686/insert 插入条数据,接着访问:http://localhost:26686/search 根据title 字段查询文档信息。
总结
实际上,Easy-Es还有很多拓展功能,比如:混合查询、原生查询、分页查询、嵌套查询、Join父子类型、获取DSL语句,同时也包含很多高阶语法:查询字段过滤、排序、聚合查询、分词查询、权重、高亮查询、GEO地理位置查询等等功能。
链接:https://juejin.cn/post/7207410405785337916