Backend Development 18 min read

How Easy-Es Transforms Elasticsearch Operations in Java: A Step‑by‑Step Guide

This article introduces Easy-Es, an Elasticsearch ORM that mimics MyBatis‑Plus syntax, explains its key features, shows how to integrate it into a Spring Boot project, and provides comprehensive code examples for index management, CRUD, simple and advanced product searches, recommendation and aggregation functionalities.

macrozheng
macrozheng
macrozheng
How Easy-Es Transforms Elasticsearch Operations in Java: A Step‑by‑Step Guide

Easy-Es Overview

Easy-Es (EE) is an ORM framework built on Elasticsearch RestHighLevelClient, designed to simplify development and improve efficiency. Its usage is very similar to MyBatis‑Plus, allowing developers familiar with MP to quickly adopt EE. Core ideas include providing simple, easy‑to‑use APIs while delegating complex operations to the framework.

Key Features

Automatic index management – developers do not need to handle index creation, updates, or migrations.

MySQL‑like syntax – only MySQL query language knowledge is required to operate ES.

Significant code reduction – typical queries require 3‑5 times less code than using RestHighLevelClient directly.

Zero magic values – field names are derived from entity classes automatically.

Low learning cost – MyBatis‑Plus users can migrate to EE without additional learning.

MySQL vs Easy-Es Syntax

Easy-Es maps common MySQL operators to ES DSL equivalents, for example:

and

must

,

or

should

,

=

term

,

!=

must_not

,

>

rangeQuery(...).gt()

,

like '%field%'

wildcardQuery

,

in

termsQuery

,

group by

AggregationBuilders.terms()

, etc.

Integration and Configuration

Add the Easy‑Es starter dependency to

pom.xml

:

<code>&lt;dependency&gt;
    &lt;groupId&gt;cn.easy-es&lt;/groupId&gt;
    &lt;artifactId&gt;easy-es-boot-starter&lt;/artifactId&gt;
    &lt;version&gt;1.0.2&lt;/version&gt;
&lt;/dependency&gt;</code>

Align ES client versions (e.g., client 7.14.0 with ES 7.17.3) in

dependencyManagement

. Configure Easy‑Es in

application.yml

:

<code>easy-es:
  enable: true
  address: localhost:9200
  banner: false</code>

Enable mapper scanning with

@EsMapperScan("com.macro.mall.tiny.easyes")

in a @Configuration class.

Using Annotations

Create a document class and annotate it:

<code>@Data
@EqualsAndHashCode
@IndexName(value = "pms", shardsNum = 1, replicasNum = 0)
public class EsProduct implements Serializable {
    @IndexId(type = IdType.CUSTOMIZE)
    private Long id;
    @IndexField(fieldType = FieldType.KEYWORD)
    private String productSn;
    @IndexField(fieldType = FieldType.KEYWORD)
    private String brandName;
    @IndexField(fieldType = FieldType.TEXT, analyzer = "ik_max_word")
    private String name;
    // other fields ...
    @IndexField(fieldType = FieldType.NESTED, nestedClass = EsProductAttributeValue.class)
    private List<EsProductAttributeValue> attrValueList;
    @Score
    private Float score;
}</code>

Define a mapper extending

BaseEsMapper&lt;EsProduct&gt;

and implement service methods for import, delete, create, and various search scenarios.

Simple Product Search

<code>@Service
public class EsProductServiceImpl implements EsProductService {
    @Autowired
    private EsProductMapper esProductMapper;
    @Override
    public PageInfo<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);
    }
}</code>

Advanced Product Search

Supports filtering by brand/category, weighted matching, and multiple sorting options (newest, sales, price asc/desc, relevance).

<code>@Override
public PageInfo<EsProduct> search(String keyword, Long brandId, Long productCategoryId, Integer pageNum, Integer pageSize, Integer sort) {
    LambdaEsQueryWrapper<EsProduct> wrapper = new LambdaEsQueryWrapper<>();
    if (brandId != null) wrapper.eq(EsProduct::getBrandId, brandId);
    if (productCategoryId != null) wrapper.eq(EsProduct::getProductCategoryId, productCategoryId).enableMust2Filter(true);
    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 != null) {
        switch (sort) {
            case 1: wrapper.orderByDesc(EsProduct::getId); break; // newest
            case 2: wrapper.orderByDesc(EsProduct::getSale); break; // sales
            case 3: wrapper.orderByAsc(EsProduct::getPrice); break; // price low→high
            case 4: wrapper.orderByDesc(EsProduct::getPrice); break; // price high→low
            default: wrapper.sortByScore(SortOrder.DESC); // relevance
        }
    }
    return esProductMapper.pageQuery(wrapper, pageNum, pageSize);
}</code>

Related Product Recommendation

<code>@Override
public PageInfo<EsProduct> recommend(Long id, Integer pageNum, Integer pageSize) {
    LambdaEsQueryWrapper<EsProduct> wrapper = new LambdaEsQueryWrapper<>();
    List<EsProduct> list = productDao.getAllEsProductList(id);
    if (!list.isEmpty()) {
        EsProduct p = list.get(0);
        wrapper.ne(EsProduct::getId, id)
               .and(i -> i.match(EsProduct::getName, p.getName(), 8f)
                           .or().match(EsProduct::getSubTitle, p.getName(), 2f)
                           .or().match(EsProduct::getKeywords, p.getName(), 2f)
                           .or().match(EsProduct::getBrandId, p.getBrandId(), 5f)
                           .or().match(EsProduct::getProductCategoryId, p.getProductCategoryId(), 3f));
    }
    return esProductMapper.pageQuery(wrapper, pageNum, pageSize);
}</code>

Aggregation Search for Related Info

Uses native RestHighLevelClient to aggregate brand names, category names, and attribute values because Easy‑Es currently supports only simple

groupBy

aggregation.

<code>SearchRequest request = new SearchRequest();
request.indices("pms_*");
SearchSourceBuilder builder = new SearchSourceBuilder();
if (StrUtil.isEmpty(keyword)) {
    builder.query(QueryBuilders.matchAllQuery());
} else {
    builder.query(QueryBuilders.multiMatchQuery(keyword, "name", "subTitle", "keywords"));
}
builder.aggregation(AggregationBuilders.terms("brandNames").field("brandName"));
builder.aggregation(AggregationBuilders.terms("productCategoryNames").field("productCategoryName"));
// nested aggregation for attributes omitted for brevity
request.source(builder);
SearchResponse response = esProductMapper.search(request, RequestOptions.DEFAULT);
// conversion logic extracts brand, category, and attribute info into EsProductRelatedInfo
</code>

Conclusion

Rewriting the previous Spring Data product‑search implementation with Easy‑Es demonstrates a more concise and readable approach, especially for CRUD and simple queries. Complex aggregations still require falling back to the native RestHighLevelClient, but overall Easy‑Es offers a MyBatis‑Plus‑like experience that greatly reduces development effort.

JavaElasticsearchSpring BootORMSearchEasy-Es
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.