一、Easy-ES概述

2022年国内团队首次发布的ES搜索引擎封装成mybatisPlus 形式,提高开发效率。

Easy-Es(简称EE)是一款由国内开发者打造并完全开源的ElasticSearch-ORM框架。在原生 RestHighLevelClient 的基础上,只做增强不做改变,为简化开发、提高效率而生。Easy-Es采用和MP一致的语法设计,降低ElasticSearch搜索引擎使用门槛,和额外学习成本,并大幅减少开发者工作量,帮助企业降本提效。在有些方面甚至比MP更简单,同时也融入了更多ElasticSearch独有的功能,助力您快速实现各种场景的开发。(也可以理解为是操作es的mp)
在这里插入图片描述

官网地址:https://www.easy-es.cn/
项目地址:https://gitee.com/dromara/easy-es/
开源社区:https://gitee.com/dromara/
开发文档:官方提供了完整的中文技术文档:

在这里插入图片描述

EE特性:

**无侵入**:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。
**损耗小**:启动即会自动注入基本 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的功能。

EE与Spring Data ES技术对比:

在这里插入图片描述

一、spring boot 整合EE 过程(Easy-ES快速入门)

1. 引入依赖

首先在 pom.xml 中添加 Easy-Es 依赖(需与 Spring Boot3 版本兼容):

<dependency>
    <groupId>org.dromara.easy-es</groupId>
    <artifactId>easy-es-boot-starter</artifactId>
    <version>最新兼容版本</version>
</dependency>

2. 启动类配置

在 Spring Boot 启动类上添加 @EnableEasyEs 注解开启 Easy-Es 功能:

package com.zgb;

import org.dromara.easyes.starter.annotation.EnableEasyEs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 应用启动类
 * @author 架构师
 */
@SpringBootApplication
@EnableEasyEs
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

二、配置文件

1. 单机版配置(application.yml)

spring:
  # Easy-Es 配置
  easy-es:
    enable: true  # 是否启用EasyEs,默认true
    address: 127.0.0.1:9200  # es连接地址,单机版
    username: elastic  # 用户名,如果es没有设置可以省略
    password: 123456   # 密码,如果es没有设置可以省略
    global-config:
      process-index-name: false  # 是否处理索引名称,默认false
      db-config:
        table-prefix: article_  # 索引前缀
        id-type: custom  # id生成策略,自定义

2. 分布式版配置(application.yml)

spring:
  # Easy-Es 配置
  easy-es:
    enable: true
    address: 192.168.1.101:9200,192.168.1.102:9200,192.168.1.103:9200  # 集群地址
    username: elastic
    password: 123456
    connection-timeout: 5000  # 连接超时时间
    socket-timeout: 30000     #  socket超时时间
    max-conn-total: 100       # 最大连接数
    global-config:
      process-index-name: false
      db-config:
        table-prefix: article_
        id-type: custom

三、核心实体类

package com.zgb.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.dromara.easyes.annotation.*;
import org.dromara.easyes.annotation.rely.FieldType;

import java.time.LocalDateTime;
import java.util.List;

/**
 * 文章索引实体类
 * 对应ES中的article索引
 * @author 架构师
 */
@Data
@IndexName(value = "article")  // 索引名称
public class Article {
    /**
     * 文章ID
     */
    @Id
    private String id;

    /**
     * 文章标题
     * 分词,可搜索,权重较高
     */
    @HighLight(mappingField = "titleHighLight")  // 高亮配置
    @IndexField(fieldType = FieldType.TEXT, analyzer = "ik_max_word", searchAnalyzer = "ik_smart", boost = 2.0f)
    private String title;
    
    /**
     * 标题高亮结果
     */
    private String titleHighLight;

    /**
     * 文章内容
     * 分词,可搜索
     */
    @HighLight(mappingField = "contentHighLight")
    @IndexField(fieldType = FieldType.TEXT, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    private String content;
    
    /**
     * 内容高亮结果
     */
    private String contentHighLight;

    /**
     * 文章摘要
     */
    @IndexField(fieldType = FieldType.TEXT, analyzer = "ik_max_word")
    private String summary;

    /**
     * 文章作者
     */
    @IndexField(fieldType = FieldType.KEYWORD)
    private String author;

    /**
     * 创建时间
     */
    @IndexField(fieldType = FieldType.DATE, dateFormat = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;

    /**
     * 文章类型
     * 如: 原创、转载、翻译
     */
    @IndexField(fieldType = FieldType.KEYWORD)
    private String type;

    /**
     * 文章标签
     */
    @IndexField(fieldType = FieldType.KEYWORD)
    private List<String> tags;

    /**
     * 点赞数
     */
    @IndexField(fieldType = FieldType.INTEGER)
    private Integer likeCount;

    /**
     * 分享数
     */
    @IndexField(fieldType = FieldType.INTEGER)
    private Integer shareCount;

    /**
     * 预览数
     */
    @IndexField(fieldType = FieldType.INTEGER)
    private Integer viewCount;
}

四、Mapper 接口

package com.zgb.mapper;

import com.zgb.entity.Article;
import org.dromara.easyes.core.core.BaseEsMapper;
import org.springframework.stereotype.Repository;

/**
 * 文章ES操作Mapper
 * 继承BaseEsMapper获得基础CRUD能力
 * @author 架构师
 */
@Repository
public interface ArticleEsMapper extends BaseEsMapper<Article> {
}

五、Service 层实现

1. Service 接口
package com.zgb.service;

import com.zgb.entity.Article;
import com.baomidou.mybatisplus.extension.service.IService;
import org.dromara.easyes.core.conditions.LambdaEsQueryWrapper;
import org.dromara.easyes.core.toolkit.PageInfo;

import java.util.List;

/**
 * 文章搜索服务接口
 * @author 架构师
 */
public interface ArticleSearchService extends IService<Article> {

    /**
     * 新增或更新文章索引
     * @param article 文章实体
     * @return 是否成功
     */
    boolean saveOrUpdateArticle(Article article);

    /**
     * 批量新增或更新文章索引
     * @param articles 文章列表
     * @return 是否成功
     */
    boolean batchSaveOrUpdateArticles(List<Article> articles);

    /**
     * 根据ID删除文章索引
     * @param id 文章ID
     * @return 是否成功
     */
    boolean deleteArticleById(String id);

    /**
     * 根据ID查询文章
     * @param id 文章ID
     * @return 文章实体
     */
    Article getArticleById(String id);

    /**
     * 简单条件查询文章列表
     * @param keyword 搜索关键词
     * @param pageNum 页码
     * @param pageSize 每页条数
     * @return 分页结果
     */
    PageInfo<Article> searchArticles(String keyword, Integer pageNum, Integer pageSize);

    /**
     * 高级条件查询
     * @param queryWrapper 查询条件构造器
     * @param pageNum 页码
     * @param pageSize 每页条数
     * @return 分页结果
     */
    PageInfo<Article> advancedSearch(LambdaEsQueryWrapper<Article> queryWrapper, Integer pageNum, Integer pageSize);
}

2. Service 实现类

package com.zgb.service.impl;

import com.zgb.entity.Article;
import com.zgb.mapper.ArticleEsMapper;
import com.zgb.service.ArticleSearchService;
import lombok.RequiredArgsConstructor;
import org.dromara.easyes.core.conditions.LambdaEsQueryWrapper;
import org.dromara.easyes.core.toolkit.PageInfo;
import org.springframework.stereotype.Service;
import java.util.List;

/**
 * 文章搜索服务实现类
 * @author 架构师
 */
@Service
@RequiredArgsConstructor
public class ArticleSearchServiceImpl implements ArticleSearchService {

    private final ArticleEsMapper articleEsMapper;

    /**
     * 新增或更新文章索引
     * @param article 文章实体
     * @return 是否成功
     */
    @Override
    public boolean saveOrUpdateArticle(Article article) {
        return articleEsMapper.insertOrUpdate(article) > 0;
    }

    /**
     * 批量新增或更新文章索引
     * @param articles 文章列表
     * @return 是否成功
     */
    @Override
    public boolean batchSaveOrUpdateArticles(List<Article> articles) {
        return articleEsMapper.insertBatch(articles) > 0;
    }

    /**
     * 根据ID删除文章索引
     * @param id 文章ID
     * @return 是否成功
     */
    @Override
    public boolean deleteArticleById(String id) {
        return articleEsMapper.deleteById(id) > 0;
    }

    /**
     * 根据ID查询文章
     * @param id 文章ID
     * @return 文章实体
     */
    @Override
    public Article getArticleById(String id) {
        return articleEsMapper.selectById(id);
    }

    /**
     * 简单条件查询文章列表
     * @param keyword 搜索关键词
     * @param pageNum 页码
     * @param pageSize 每页条数
     * @return 分页结果
     */
    @Override
    public PageInfo<Article> searchArticles(String keyword, Integer pageNum, Integer pageSize) {
        LambdaEsQueryWrapper<Article> wrapper = new LambdaEsQueryWrapper<>();
        // 标题或内容包含关键词
        wrapper.match(Article::getTitle, keyword)
               .or()
               .match(Article::getContent, keyword);
        // 按创建时间倒序
        wrapper.orderByDesc(Article::getCreateTime);
        return articleEsMapper.pageQuery(wrapper, pageNum, pageSize);
    }

    /**
     * 高级条件查询
     * @param queryWrapper 查询条件构造器
     * @param pageNum 页码
     * @param pageSize 每页条数
     * @return 分页结果
     */
    @Override
    public PageInfo<Article> advancedSearch(LambdaEsQueryWrapper<Article> queryWrapper, Integer pageNum, Integer pageSize) {
        return articleEsMapper.pageQuery(queryWrapper, pageNum, pageSize);
    }
}

六、高级查询示例(Controller层)

package com.zgb.controller;

import com.zgb.entity.Article;
import com.zgb.service.ArticleSearchService;
import com.dromara.easyes.core.conditions.LambdaEsQueryWrapper;
import com.dromara.easyes.core.toolkit.PageInfo;
import lombok.RequiredArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.List;

/**
 * 文章搜索控制器
 * @author 架构师
 */
@RestController
@RequestMapping("/api/article")
@RequiredArgsConstructor
public class ArticleSearchController {

    private final ArticleSearchService articleSearchService;

    /**
     * 创建或更新文章
     */
    @PostMapping
    public boolean saveOrUpdate(@RequestBody Article article) {
        return articleSearchService.saveOrUpdateArticle(article);
    }

    /**
     * 删除文章
     */
    @DeleteMapping("/{id}")
    public boolean delete(@PathVariable String id) {
        return articleSearchService.deleteArticleById(id);
    }

    /**
     * 获取文章详情
     */
    @GetMapping("/{id}")
    public Article getById(@PathVariable String id) {
        return articleSearchService.getArticleById(id);
    }

    /**
     * 简单搜索
     */
    @GetMapping("/search")
    public PageInfo<Article> search(
            @RequestParam String keyword,
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {
        return articleSearchService.searchArticles(keyword, pageNum, pageSize);
    }

    /**
     * 高级搜索示例
     * 演示复杂查询条件组合
     */
    @GetMapping("/advanced-search")
    public PageInfo<Article> advancedSearch(
            @RequestParam(required = false) String keyword,
            @RequestParam(required = false) String author,
            @RequestParam(required = false) String type,
            @RequestParam(required = false) List<String> tags,
            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime,
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {
        
        LambdaEsQueryWrapper<Article> wrapper = new LambdaEsQueryWrapper<>();
        
        // 关键词匹配(标题或内容)
        if (keyword != null && !keyword.isEmpty()) {
            wrapper.match(Article::getTitle, keyword)
                   .or()
                   .match(Article::getContent, keyword);
        }
        
        // 作者精确匹配
        if (author != null && !author.isEmpty()) {
            wrapper.eq(Article::getAuthor, author);
        }
        
        // 文章类型精确匹配
        if (type != null && !type.isEmpty()) {
            wrapper.eq(Article::getType, type);
        }
        
        // 标签包含(任一标签)
        if (tags != null && !tags.isEmpty()) {
            wrapper.in(Article::getTags, tags);
        }
        
        // 时间范围查询
        if (startTime != null) {
            wrapper.ge(Article::getCreateTime, startTime);
        }
        if (endTime != null) {
            wrapper.le(Article::getCreateTime, endTime);
        }
        
        // 排序:优先按点赞数降序,再按创建时间降序
        wrapper.orderByDesc(Article::getLikeCount)
               .orderByDesc(Article::getCreateTime);
        
        // 高亮设置(全局已配置,这里可以覆盖)
        wrapper.highLight(true);
        
        return articleSearchService.advancedSearch(wrapper, pageNum, pageSize);
    }
}

七、总结

以上实现了 Spring Boot3 与 Easy-Es 的完整整合,包括:

  1. 单机版与分布式版的配置方案
  2. 标准的文章索引结构设计
  3. 完整的 CRUD 基础操作
  4. 包含高亮、多条件组合、范围查询、排序等的高级查询

Easy-Es 作为 Elasticsearch 的 ORM 框架,极大简化了 ES 操作,通过类似 MyBatis-Plus 的 API 风格,降低了学习成本,提高了开发效率。在实际项目中,可根据业务需求进一步扩展查询条件和索引设计。

如果文章对你有一点点帮助,欢迎【点赞、留言、+ 关注】
您的关注是我创作的动力!若有疑问、交流、需求,欢迎留言私聊!
多一个朋友多一条路!

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐