MyBatis-Plus同款Elasticsearch ORM框架,用起来够优雅

使用过Spring Data操作ES的小伙伴应该有所了解,它只能实现一些非常基本的数据管理工作,一旦遇到稍微复杂点的查询,基本都要依赖ES官方提供的RestHighLevelClient,Spring Data只是在其基础上进行了简单的封装。最近发现一款更优雅的ES ORM框架Easy-Es,使用它能像MyBatis-Plus一样操作ES,今天就以mall项目中的商品搜索功能为例,来聊聊它的使用!

Easy-Es简介

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

EE的主要特性如下:

MySQL与Easy-Es语法对比

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

MySQL

Easy-Es

es-DSL/es java api

and

and

must

or

or

should

=

eq

term

!=

ne

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

...

...

...

集成及配置

接下来把Easy-Es集成到项目中配置下就可以使用了。


    cn.easy-es
    easy-es-boot-starter
    1.0.2

由于底层使用了ES官方提供的RestHighLevelClient,这里ES的相关依赖版本需要统一下,这里使用的ES客户端版本为7.14.0,ES版本为7.17.3


    
        
            org.elasticsearch.client
            elasticsearch-rest-high-level-client
            7.14.0
        
        
            org.elasticsearch
            elasticsearch
            7.14.0
        
    

再修改配置文件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的话,需要和它的扫描路径区分开来。

/**
 * EasyEs配置类
 * Created by macro on 2022/9/16.
 */
@Configuration
@EsMapperScan("com.macro.mall.tiny.easyes")
public class EasyEsConfig {
}

使用

Easy-Es集成和配置完成后,就可以开始使用了。这里还是以mall项目的商品搜索功能为例,聊聊Easy-Es的使用,Spring Data的实现方式可以参考Elasticsearch项目实战,商品搜索功能设计与实现! 。

注解的使用

下面我们来学习下Easy-Es中注解的使用。

/**
 * 搜索商品的信息
 * Created by macro on 2018/6/19.
 */
@Data
@EqualsAndHashCode
@IndexName(value = "pms", shardsNum = 1, replicasNum = 0)
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, nestedClass = EsProductAttributeValue.class)
    private List attrValueList;
    @Score
    private Float score;
}

注解名称

用途

参数

@IndexName

索引名注解

value:指定索引名;shardsNum:分片数;replicasNum:副本数

@IndexId

ES主键注解

type:指定注解类型,CUSTOMIZE表示自定义

@IndexField

ES字段注解

fieldType:字段在索引中的类型;analyzer:索引文档时用的分词器;nestedClass:嵌套类

@Score

得分注解

decimalPlaces:得分保留小数位,实体类中被作为ES查询得分返回的字段使用

/**
 * 搜索商品的属性信息
 * Created by macro on 2018/6/27.
 */
@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;
}

商品信息维护

下面我们来实现几个简单的商品信息维护接口,包括商品信息的导入、创建和删除。

/**
 * 商品ES操作类
 * Created by macro on 2018/6/19.
 */
public interface EsProductMapper extends BaseEsMapper {

}

然后在Service实现类中直接使用EsProductMapper内置方法实现即可,是不是和MyBatis-Plus的用法一致?

/**
 * 搜索商品管理Service实现类
 * Created by macro on 2018/6/19.
 */
@Service
public class EsProductServiceImpl implements EsProductService {
    @Autowired
    private EsProductDao productDao;
    @Autowired
    private EsProductMapper esProductMapper;
    @Override
    public int importAll() {
        List esProductList = productDao.getAllEsProductList(null);
        return esProductMapper.insertBatch(esProductList);
    }

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

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

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

简单商品搜索

下面我们来实现一个最简单的商品搜索,分页搜索商品名称、副标题、关键词中包含指定关键字的商品。

/**
 * 搜索商品管理Service实现类
 * Created by macro on 2018/6/19.
 */
@Service
public class EsProductServiceImpl implements EsProductService {
    @Autowired
    private EsProductMapper esProductMapper;
    @Override
    public PageInfo search(String keyword, Integer pageNum, Integer pageSize) {
        LambdaEsQueryWrapper 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语句

综合商品搜索

下面我们来实现一个复杂的商品搜索,涉及到过滤、不同字段匹配权重不同以及可以进行排序。

/**
 * 搜索商品管理Service实现类
 * Created by macro on 2018/6/19.
 */
@Service
public class EsProductServiceImpl implements EsProductService {
    @Autowired
    private EsProductMapper esProductMapper;
    @Override
    public PageInfo search(String keyword, Long brandId, Long productCategoryId, Integer pageNum, Integer pageSize,Integer sort) {
        LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>();
        //过滤
        if (brandId != null || productCategoryId != null) {
            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==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);
    }
}

相关商品推荐

当我们查看相关商品的时候,一般底部会有一些商品推荐,这里简单来实现下。

/**
 * 搜索商品管理Service实现类
 * Created by macro on 2018/6/19.
 */
@Service
public class EsProductServiceImpl implements EsProductService {
    @Autowired
    private EsProductMapper esProductMapper;
    @Override
    public PageInfo recommend(Long id, Integer pageNum, Integer pageSize) {
        LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>();
        List esProductList = productDao.getAllEsProductList(id);
        if (esProductList.size() > 0) {
            EsProduct esProduct = esProductList.get(0);
            String keyword = esProduct.getName();
            Long brandId = esProduct.getBrandId();
            Long productCategoryId = esProduct.getProductCategoryId();
            //用于过滤掉相同的商品
            wrapper.ne(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);
    }
}

聚合搜索商品相关信息

在搜索商品时,经常会有一个筛选界面来帮助我们找到想要的商品,这里我们来简单实现下。

/**
 * 搜索商品管理Service实现类
 * Created by macro on 2018/6/19.
 */
@Service
public class EsProductServiceImpl implements EsProductService {
    @Autowired
    private EsProductMapper esProductMapper;
    @Override
    public EsProductRelatedInfo searchRelatedInfo(String keyword) {
        SearchRequest searchRequest = new SearchRequest();
        searchRequest.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"));
        //聚合搜索商品属性,去除type=1的属性
        AbstractAggregationBuilder aggregationBuilder = AggregationBuilders.nested("allAttrValues", "attrValueList")
                .subAggregation(AggregationBuilders.filter("productAttrs", QueryBuilders.termQuery("attrValueList.type", 1))
                        .subAggregation(AggregationBuilders.terms("attrIds")
                                .field("attrValueList.productAttributeId")
                                .subAggregation(AggregationBuilders.terms("attrValues")
                                        .field("attrValueList.value"))
                                .subAggregation(AggregationBuilders.terms("attrNames")
                                        .field("attrValueList.name"))));
        builder.aggregation(aggregationBuilder);
        searchRequest.source(builder);
        try {
            SearchResponse searchResponse = esProductMapper.search(searchRequest, RequestOptions.DEFAULT);
            return convertProductRelatedInfo(searchResponse);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 将返回结果转换为对象
     */
    private EsProductRelatedInfo convertProductRelatedInfo(SearchResponse response) {
        EsProductRelatedInfo productRelatedInfo = new EsProductRelatedInfo();
        Map aggregationMap = response.getAggregations().asMap();
        //设置品牌
        Aggregation brandNames = aggregationMap.get("brandNames");
        List brandNameList = new ArrayList<>();
        for(int i = 0; i<((Terms) brandNames).getBuckets().size(); i++){
            brandNameList.add(((Terms) brandNames).getBuckets().get(i).getKeyAsString());
        }
        productRelatedInfo.setBrandNames(brandNameList);
        //设置分类
        Aggregation productCategoryNames = aggregationMap.get("productCategoryNames");
        List productCategoryNameList = new ArrayList<>();
        for(int i=0;i<((Terms) productCategoryNames).getBuckets().size();i++){
            productCategoryNameList.add(((Terms) productCategoryNames).getBuckets().get(i).getKeyAsString());
        }
        productRelatedInfo.setProductCategoryNames(productCategoryNameList);
        //设置参数
        Aggregation productAttrs = aggregationMap.get("allAttrValues");
        List<? extends Terms.Bucket> attrIds = ((ParsedStringTerms) ((ParsedFilter) ((ParsedNested) productAttrs).getAggregations().get("productAttrs")).getAggregations().get("attrIds")).getBuckets();
        List attrList = new ArrayList<>();
        for (Terms.Bucket attrId : attrIds) {
            EsProductRelatedInfo.ProductAttr attr = new EsProductRelatedInfo.ProductAttr();
            attr.setAttrId(Long.parseLong((String) attrId.getKey()));
            List attrValueList = new ArrayList<>();
            List<? extends Terms.Bucket> attrValues = ((ParsedStringTerms) attrId.getAggregations().get("attrValues")).getBuckets();
            List<? extends Terms.Bucket> attrNames = ((ParsedStringTerms) attrId.getAggregations().get("attrNames")).getBuckets();
            for (Terms.Bucket attrValue : attrValues) {
                attrValueList.add(attrValue.getKeyAsString());
            }
            attr.setAttrValues(attrValueList);
            if(!CollectionUtils.isEmpty(attrNames)){
                String attrName = attrNames.get(0).getKeyAsString();
                attr.setAttrName(attrName);
            }
            attrList.add(attr);
        }
        productRelatedInfo.setProductAttrs(attrList);
        return productRelatedInfo;
    }
}

总结

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

展开阅读全文

页面更新:2024-03-17

标签:框架   权重   字段   商品信息   语法   属性   优雅   名称   简单   品牌   商品

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top