Easy-Es简介

Easy-Es(简称EE)是一款基于Elasticsearch(简称ES)官方提供的RestHighLevelClient打造的ORM开发框架,在RestHighLevelClient的基础上,只做增强不做改变,为简化开发、提高效率而生。EE和Mybatis-Plus(简称MP)的用法非常相似,如果你之前使用过MP的话,应该能很快上手EE。EE的理念是:把简单、易用、方便留给用户,把复杂留给框架。

EE的主要特性如下:

  • 全自动索引托管:开发者无需关心索引的创建、更新及数据迁移等繁琐步骤,框架能自动完成。
  • 屏蔽语言差异:开发者只需要会MySQL的语法即可使用ES。
  • 代码量极少:与直接使用官方提供的RestHighLevelClient相比,相同的查询平均可以节省3-5倍的代码量。
  • 零魔法值:字段名称直接从实体中获取,无需手写。
  • 零额外学习成本: 开发者只要会国内最受欢迎的Mybatis-Plus用法,即可无缝迁移至EE。

MySQL与Easy-Es语法对比

首先我们来对MySQL、Easy-Es和RestHighLevelClient的语法做过对比,来快速学习下Easy-Es的语法。

MySQLEasy-Eses-DSL/es java api
andandmust
ororshould
=eqterm
!=notboolQueryBuilder.mustNot(queryBuilder)
gtQueryBuilders.rangeQuery('es field').gt()
>=ge.rangeQuery('es field').gte()
<lt.rangeQuery('es field').lt()
<=le.rangeQuery('es field').lte()
like '%field%'likeQueryBuilders.wildcardQuery(field,value)
not like '%field%'notLikemust not wildcardQuery(field,value)
like '%field'likeLeftQueryBuilders.wildcardQuery(field,*value)
like 'field%'likeRightQueryBuilders.wildcardQuery(field,value*)
betweenbetweenQueryBuilders.rangeQuery('es field').from(xx).to(xx)
notBetweennotBetweenmust not QueryBuilders.rangeQuery('es field').from(xx).to(xx)
is nullisNullmust not QueryBuilders.existsQuery(field)
is notNullisNotNullQueryBuilders.existsQuery(field)
ininQueryBuilders.termsQuery(" xx es field", xx)
not innotInmust not QueryBuilders.termsQuery(" xx es field", xx)
group bygroupByAggregationBuilders.terms()
order byorderByfieldSortBuilder.order(ASC/DESC)
minminAggregationBuilders.min
maxmaxAggregationBuilders.max
avgavgAggregationBuilders.avg
sumsumAggregationBuilders.sum
order by xxx ascorderByAscfieldSortBuilder.order(SortOrder.ASC)
order by xxx descorderByDescfieldSortBuilder.order(SortOrder.DESC)
-matchmatchQuery
-matchPhraseQueryBuilders.matchPhraseQuery
-matchPrefixQueryBuilders.matchPhrasePrefixQuery
-queryStringQueryQueryBuilders.queryStringQuery
select *matchAllQueryQueryBuilders.matchAllQuery()
-highLightHighlightBuilder.Field
.........

集成及配置

  • 首先需要在pom.xml中添加Easy-Es的相关依赖;
<dependency>
    <groupId>org.dromara.easy-es</groupId>
    <artifactId>easy-es-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>
  • 由于底层使用了ES官方提供的RestHighLevelClient,这里ES的相关依赖版本需要统一下,这里使用的ES客户端版本为7.17.28,ES版本为7.17.3
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.17.28</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>7.17.28</version>
        </dependency>
    </dependencies>
</dependencyManagement>
  • 再修改配置文件application.yml对Easy-Es进行配置。
easy-es:
  # 是否开启EE自动配置
  enable: true
  # ES连接地址+端口
  address: localhost:9200
  # 关闭自带banner
  banner: false
  • 添加Easy-Es的Java配置,使用@EsMapperScan配置好Easy-Es的Mapper接口和文档对象路径,如果你使用了MyBatis-Plus的话,需要和它的扫描路径区分开来。
/**
 * @auther macrozheng
 * @description EasyEs配置类
 * @date 2025/9/4
 * @github https://github.com/macrozheng
 */
@Configuration
@EsMapperScan("com.macro.blog.easyes")
public class EasyEsConfig {
}

使用

mall项目介绍

这里还是简单介绍下mall项目吧,mall项目是一套基于 SpringBoot + Vue + uni-app 实现的电商系统(Github标星60K),采用Docker容器化部署,后端支持多模块和微服务架构。包括前台商城项目和后台管理系统,能支持完整的订单流程!涵盖商品、订单、购物车、权限、优惠券、会员、支付等功能!

  • Boot项目:github.com/macrozheng/…
  • Cloud项目:github.com/macrozheng/…
  • 教程网站:www.macrozheng.com

项目演示:

注解的使用

  • 首先我们需要创建文档对象EsProduct,然后给类和字段添加上Easy-Es的注解;
/**
 * @auther macrozheng
 * @description 搜索商品的信息
 * @date 2025/9/4
 * @github https://github.com/macrozheng
 */
@Data
@EqualsAndHashCode
@IndexName(value = "es_product")
public class EsProduct implements Serializable {
    private static final long serialVersionUID = -1L;
    @IndexId(type = IdType.CUSTOMIZE)
    private Long id;
    @IndexField(fieldType = FieldType.KEYWORD)
    private String productSn;
    private Long brandId;
    @IndexField(fieldType = FieldType.KEYWORD)
    private String brandName;
    private Long productCategoryId;
    @IndexField(fieldType = FieldType.KEYWORD)
    private String productCategoryName;
    private String pic;
    @IndexField(fieldType = FieldType.TEXT, analyzer = "ik_max_word")
    private String name;
    @IndexField(fieldType = FieldType.TEXT, analyzer = "ik_max_word")
    private String subTitle;
    @IndexField(fieldType = FieldType.TEXT, analyzer = "ik_max_word")
    private String keywords;
    private BigDecimal price;
    private Integer sale;
    private Integer newStatus;
    private Integer recommandStatus;
    private Integer stock;
    private Integer promotionType;
    private Integer sort;
    @IndexField(fieldType = FieldType.NESTED, nestedOrObjectClass = EsProductAttributeValue.class)
    private List<EsProductAttributeValue> attrValueList;
}
  • EsProduct中的注解具体说明如下:
注解名称用途参数
@IndexName索引名注解value:指定索引名;shardsNum:分片数;replicasNum:副本数
@IndexIdES主键注解type:指定注解类型,CUSTOMIZE表示自定义
@IndexFieldES字段注解fieldType:字段在索引中的类型;analyzer:索引文档时用的分词器;nestedClass:嵌套类
  • EsProduct中嵌套类型EsProductAttributeValue的代码如下。
/**
 * @auther macrozheng
 * @description 搜索商品的属性信息
 * @date 2025/9/4
 * @github https://github.com/macrozheng
 */
@Data
@EqualsAndHashCode
public class EsProductAttributeValue implements Serializable {
    private static final long serialVersionUID = 1L;
    @IndexField(fieldType = FieldType.LONG)
    private Long id;
    @IndexField(fieldType = FieldType.KEYWORD)
    private Long productAttributeId;
    //属性值
    @IndexField(fieldType = FieldType.KEYWORD)
    private String value;
    //属性参数:0->规格;1->参数
    @IndexField(fieldType = FieldType.INTEGER)
    private Integer type;
    //属性名称
    @IndexField(fieldType=FieldType.KEYWORD)
    private String name;
}

商品信息维护

  • 首先我们需要定义一个Mapper,继承BaseEsMapper;
/**
 * @auther macrozheng
 * @description 商品ES操作类
 * @date 2025/9/4
 * @github https://github.com/macrozheng
 */
public interface EsProductMapper extends BaseEsMapper<EsProduct> {

}
  • 然后在Service实现类中直接使用EsProductMapper内置方法实现即可,是不是和MyBatis-Plus的用法一致?
/**
 * @auther macrozheng
 * @description 搜索商品管理Service实现类
 * @date 2025/9/4
 * @github https://github.com/macrozheng
 */
@Service
public class EsProductServiceImpl implements EsProductService {
    private static final Logger LOGGER = LoggerFactory.getLogger(EsProductServiceImpl.class);
    @Autowired
    private EsProductMapper esProductMapper;
    @Override
    public int importAll() {
        return esProductMapper.insertBatch(getAllEsProductList(null));
    }

    @Override
    public void delete(Long id) {
        esProductMapper.deleteById(id);
    }

    @Override
    public EsProduct create(Long id) {
        EsProduct result = null;
        List<EsProduct> esProductList = getAllEsProductList(id);
        if (esProductList.size() > 0) {
            result = esProductList.get(0);
            esProductMapper.insert(result);
        }
        return result;
    }

    @Override
    public void delete(List<Long> ids) {
        if (!CollectionUtils.isEmpty(ids)) {
            esProductMapper.deleteBatchIds(ids);
        }
    }
}

简单商品搜索

  • 通过QueryWrapper来构造查询条件,然后使用Mapper中的方法来进行查询,使用过MyBatis-Plus的小伙伴应该很熟悉了;
/**
 * @auther macrozheng
 * @description 搜索商品管理Service实现类
 * @date 2025/9/4
 * @github https://github.com/macrozheng
 */
@Service
public class EsProductServiceImpl implements EsProductService {
    @Autowired
    private EsProductMapper esProductMapper;
    @Override
    public EsPageInfo<EsProduct> search(String keyword, Integer pageNum, Integer pageSize) {
        LambdaEsQueryWrapper<EsProduct> wrapper = new LambdaEsQueryWrapper<>();
        if(StrUtil.isEmpty(keyword)){
            wrapper.matchAllQuery();
        }else{
            wrapper.multiMatchQuery(keyword,EsProduct::getName,EsProduct::getSubTitle,EsProduct::getKeywords);
        }
        return esProductMapper.pageQuery(wrapper, pageNum, pageSize);
    }
}
  • 使用Swagger访问接口后,可以在控制台输出查看生成的DSL语句,访问地址:http://localhost:8088/swagger-ui.html

  • 把DSL语句直接复制Kibana中即可执行查看结果了,这和我们手写DSL语句没什么两样的。

综合商品搜索

  • 首先来说需求,按输入的关键字搜索商品名称(权重10)、副标题(权重5)和关键词(权重2),可以按品牌和分类进行筛选,可以有5种排序方式,默认按相关度进行排序,看下接口文档有助于理解;

  • 这个功能之前使用Spring Data来实现非常复杂,使用Easy-Es来实现确实简洁不少,下面是使用Easy-Es的实现方式;
/**
 * @auther macrozheng
 * @description 搜索商品管理Service实现类
 * @date 2025/9/4
 * @github https://github.com/macrozheng
 */
@Service
public class EsProductServiceImpl implements EsProductService {
    @Autowired
    private EsProductMapper esProductMapper;
       @Override
    public EsPageInfo<EsProduct> search(String keyword, Long brandId, Long productCategoryId, Integer pageNum, Integer pageSize,Integer sort) {
        LambdaEsQueryWrapper<EsProduct> wrapper = new LambdaEsQueryWrapper<>();
        //过滤
        if (brandId != null || productCategoryId != null) {
            if (brandId != null) {
                wrapper.eq(EsProduct::getBrandId,brandId);
            }
            if (productCategoryId != null) {
                wrapper.eq(EsProduct::getProductCategoryId,productCategoryId);
            }
        }
        //搜索
        if (StrUtil.isEmpty(keyword)) {
            wrapper.matchAllQuery();
        } else {
            wrapper.and(i -> i.match(EsProduct::getName, keyword, 10f)
                    .or().match(EsProduct::getSubTitle, keyword, 5f)
                    .or().match(EsProduct::getKeywords, keyword, 2f));
        }
        //排序
        if(sort==1){
            //按新品从新到旧
            wrapper.orderByDesc(EsProduct::getId);
        }else if(sort==2){
            //按销量从高到低
            wrapper.orderByDesc(EsProduct::getSale);
        }else if(sort==3){
            //按价格从低到高
            wrapper.orderByAsc(EsProduct::getPrice);
        }else if(sort==4){
            //按价格从高到低
            wrapper.orderByDesc(EsProduct::getPrice);
        }else{
            //按相关度
            wrapper.sortByScore(SortOrder.Desc);
        }
        return esProductMapper.pageQuery(wrapper, pageNum, pageSize);
    }
}
  • 再对比下之前使用Spring Data的实现方式,没有QueryWrapper来构造条件,还要硬编码字段名称,确实优雅了不少!

相关商品推荐

  • 首先来说下需求,可以根据指定商品的ID来查找相关商品,看下接口文档有助于理解;

  • 这里我们的实现原理是这样的:首先根据ID获取指定商品信息,然后以指定商品的名称、品牌和分类来搜索商品,并且要过滤掉当前商品,调整搜索条件中的权重以获取最好的匹配度;

  • 使用Easy-Es来实现依旧是那么简洁!

/**
 * @auther macrozheng
 * @description 搜索商品管理Service实现类
 * @date 2025/9/4
 * @github https://github.com/macrozheng
 */
@Service
public class EsProductServiceImpl implements EsProductService {
    @Autowired
    private EsProductMapper esProductMapper;
    @Override
    public EsPageInfo<EsProduct> recommend(Long id, Integer pageNum, Integer pageSize) {
        LambdaEsQueryWrapper<EsProduct> wrapper = new LambdaEsQueryWrapper<>();
        List<EsProduct> esProductList = getAllEsProductList(id);
        if (esProductList.size() > 0) {
            EsProduct esProduct = esProductList.get(0);
            String keyword = esProduct.getName();
            Long brandId = esProduct.getBrandId();
            Long productCategoryId = esProduct.getProductCategoryId();
            //用于过滤掉相同的商品
            wrapper.not().eq(EsProduct::getId,id);
            //根据商品标题、品牌、分类进行搜索
            wrapper.and(i -> i.match(EsProduct::getName, keyword, 8f)
                    .or().match(EsProduct::getSubTitle, keyword, 2f)
                    .or().match(EsProduct::getKeywords, keyword, 2f)
                    .or().match(EsProduct::getBrandId, brandId, 5f)
                    .or().match(EsProduct::getProductCategoryId, productCategoryId, 3f));
            return esProductMapper.pageQuery(wrapper, pageNum, pageSize);
        }
        return esProductMapper.pageQuery(wrapper, pageNum, pageSize);
    }
}

总结

今天将之前的使用Spring Data的商品搜索案例使用Easy-Es改写了一下,确实使用Easy-Es更简单,但是对于复杂的聚合搜索功能,两者都需要使用原生的RestHighLevelClient用法来实现。使用Easy-Es来操作ES确实足够优雅,它类似MyBatis-Plus的用法能大大降低我们的学习成本,快速完成开发工作!

参考资料

官方文档:www.easy-es.cn/

项目源码地址

github.com/macrozheng/…

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]