Simplify Elasticsearch with Easy-Es: MyBatis‑Plus‑Style ORM Tutorial

This article introduces Easy-Es, an Elasticsearch ORM that mimics MyBatis‑Plus syntax, walks through its core features, integration steps, entity annotations, mapper and service implementations, and demonstrates simple, advanced, and recommendation search scenarios, highlighting the reduction in boilerplate code.

macrozheng
macrozheng
macrozheng
Simplify Elasticsearch with Easy-Es: MyBatis‑Plus‑Style ORM Tutorial

Easy-Es Overview

Easy-Es (EE) is an ORM framework built on Elasticsearch's RestHighLevelClient. It provides MyBatis‑Plus‑like APIs, allowing developers familiar with MyBatis‑Plus to quickly adopt EE. Its philosophy is to keep simple, easy‑to‑use features for users while handling complex details internally.

Key Features

Automatic index management – developers do not need to create, update or migrate indices manually.

SQL‑like syntax – only MySQL syntax is required to operate Elasticsearch.

Reduced code – EE can save 3‑5 times the code compared with using RestHighLevelClient directly.

No magic values – field names are obtained directly from entity classes.

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

MySQL vs Easy-Es Syntax Comparison

MySQL

Easy-Es

es-DSL/es java api

and

and

must

or

or

should

=

eq

term

!=

not

boolQueryBuilder.mustNot(queryBuilder)

>

gt

QueryBuilders.rangeQuery('es field').gt()

>=

ge

.rangeQuery('es field').gte()

<

lt

.rangeQuery('es field').lt()

<=

le

.rangeQuery('es field').lte()

like '%field%'

like

QueryBuilders.wildcardQuery(field, value)

not like '%field%'

notLike

must not wildcardQuery(field, value)

like '%field'

likeLeft

QueryBuilders.wildcardQuery(field,*value)

like 'field%'

likeRight

QueryBuilders.wildcardQuery(field,value*)

between

between

QueryBuilders.rangeQuery('es field').from(xx).to(xx)

notBetween

notBetween

must not QueryBuilders.rangeQuery('es field').from(xx).to(xx)

is null

isNull

must not QueryBuilders.existsQuery(field)

is notNull

isNotNull

QueryBuilders.existsQuery(field)

in

in

QueryBuilders.termsQuery("xx es field", xx)

not in

notIn

must not QueryBuilders.termsQuery("xx es field", xx)

group by

groupBy

AggregationBuilders.terms()

order by

orderBy

fieldSortBuilder.order(ASC/DESC)

min

min

AggregationBuilders.min

max

max

AggregationBuilders.max

avg

avg

AggregationBuilders.avg

sum

sum

AggregationBuilders.sum

order by xxx asc

orderByAsc

fieldSortBuilder.order(SortOrder.ASC)

order by xxx desc

orderByDesc

fieldSortBuilder.order(SortOrder.DESC)

-

match

matchQuery

-

matchPhrase

QueryBuilders.matchPhraseQuery

-

matchPrefix

QueryBuilders.matchPhrasePrefixQuery

-

queryStringQuery

QueryBuilders.queryStringQuery

select *

matchAllQuery

QueryBuilders.matchAllQuery()

-

highLight

HighlightBuilder.Field

Integration and Configuration

Add the Easy-Es starter dependency to pom.xml:

<dependency>
  <groupId>org.dromara.easy-es</groupId>
  <artifactId>easy-es-boot-starter</artifactId>
  <version>3.0.0</version>
</dependency>

Set the Elasticsearch client version to 7.17.28 and the server version to 7.17.3 in dependencyManagement:

<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>

Configure EE in application.yml:

easy-es:
  enable: true
  address: localhost:9200
  banner: false

Enable mapper scanning and define a configuration class:

@Configuration
@EsMapperScan("com.macro.blog.easyes")
public class EasyEsConfig {
}

Entity Annotations

Create a document class EsProduct and annotate it with EE annotations such as @IndexName, @IndexId, and @IndexField. Example:

@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;
}

The nested type EsProductAttributeValue is defined similarly:

@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;
    @IndexField(fieldType = FieldType.INTEGER)
    private Integer type;
    @IndexField(fieldType = FieldType.KEYWORD)
    private String name;
}

Mapper and Service Implementation

Define a mapper interface extending BaseEsMapper<EsProduct>:

public interface EsProductMapper extends BaseEsMapper<EsProduct> {}

Implement service methods for importing, creating, deleting, and searching products. Example of a simple search method:

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);
}

Advanced search combines filters, weighted field matching, and sorting based on user‑selected criteria:

public EsPageInfo<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); }
    if (StrUtil.isEmpty(keyword)) {
        wrapper.matchAllQuery();
    } else {
        wrapper.and(w -> w.match(EsProduct::getName, keyword, 10f)
                         .or().match(EsProduct::getSubTitle, keyword, 5f)
                         .or().match(EsProduct::getKeywords, keyword, 2f));
    }
    // Sorting logic (by newness, sales, price, or relevance) omitted for brevity
    return esProductMapper.pageQuery(wrapper, pageNum, pageSize);
}

Recommendation method searches for related products based on the current product’s name, brand, and category while excluding the current product:

public EsPageInfo<EsProduct> recommend(Long id, Integer pageNum, Integer pageSize) {
    LambdaEsQueryWrapper<EsProduct> wrapper = new LambdaEsQueryWrapper<>();
    List<EsProduct> list = getAllEsProductList(id);
    if (!list.isEmpty()) {
        EsProduct p = list.get(0);
        wrapper.not().eq(EsProduct::getId, id)
               .and(w -> w.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);
}

Conclusion

Rewriting the previous Spring Data product search with Easy‑Es demonstrates that EE greatly simplifies code while still allowing complex aggregation queries through the underlying RestHighLevelClient when needed.

References

Official documentation: https://www.easy-es.cn/

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaElasticsearchSpring BootORMEasy-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

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.