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.
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><dependency>
<groupId>cn.easy-es</groupId>
<artifactId>easy-es-boot-starter</artifactId>
<version>1.0.2</version>
</dependency></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<EsProduct>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
groupByaggregation.
<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.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.