Elasticsearch 实战2:ES 项目实战(二):基本操作、批处理、高级查询

导读:上篇博客讲到了Java 集成 Spring Data Elasticsearch 的简介、环境搭建和domain 实体类的编写,本篇博客将接着讲解 如何用 Java 实现 es 基本操作、批处理、高级查询。(本文👆上部有完整的实例代码)。若有不懂的请结合之前的博客进行阅读。
以下为我编写的关于es 的基础通用功能代码,基本涵盖了es 的所有操作,之后的示例将以此基础进行讲解。

  • IBaseService :提供公共的,基础的 ElasticSearch 功能
  • BaseServiceImpl:IBaseService 实现类
  • BaseProductEsTest:基础功能测试类
  • ProductEsTest:各种高级查询功能测试类

1、接口类 IBaseService

package com.xinghua.elasticsearchservice.common.service;

import com.xinghua.elasticsearchservice.common.dto.SortDTO;
import com.xinghua.elasticsearchservice.common.model.EntityEsModel;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.aggregations.Aggregations;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;

import java.util.List;

/**
 * @Description 提供公共的,基础的 ElasticSearch 功能
 * @Author 姚广星
 * @Date 2020/2/24 16:57
 **/
public interface IBaseService<T extends EntityEsModel> {

    /**
     * 获取ES索引名称
     *
     * @return
     */
    String getIndexName();

    /**
     * 获取ES索引类型
     *
     * @return
     */
    String getIndexType();


    /**
     * 返回泛型上的Class对象
     *
     * @return
     */
    Class<T> getEntityClass();

    /**
     * 初始化数据
     * 1: 删除原有的索引
     * 2: 创建索引并且初始化映射
     * 3: bulk 批量初始化数据
     *
     * @param entityList
     * @throws Exception
     */
    void init(List<T> entityList);

    /**
     * 创建索引和映射(若原有索引存在则删除重新创建)
     *
     * @return
     */
    Boolean createEntityEsIndex();

    /**
     * 新增或修改
     *
     * @param entityModel
     * @return
     */
    String saveOrUpdate(T entityModel);

    /**
     * 删除entity
     *
     * @param id
     * @return
     */
    String delete(String id);

    /**
     * 删除索引
     *
     * @return
     */
    Boolean deleteIndex();


    /**
     * 批量新增/更新
     *
     * @param entityModelList
     */
    void batchInsertOrUpdate(List<T> entityModelList);

    /**
     * 分页查询
     *
     * @param nativeSearchQueryBuilder 查询条件
     * @param pageNumber               页数
     * @param pageSize                 页码
     * @return
     */
    Page<T> searchPage(NativeSearchQueryBuilder nativeSearchQueryBuilder, int pageNumber, int pageSize);

    /**
     * 分页查询 按照指定字段排序,多个字段按照先后顺序排序
     *
     * @param sortDTOList              排序条件
     * @param nativeSearchQueryBuilder 查询条件
     * @param pageNumber               页数
     * @param pageSize                 页码
     * @return
     */
    Page<T> searchPageBySort(List<SortDTO> sortDTOList, NativeSearchQueryBuilder nativeSearchQueryBuilder, int pageNumber, int pageSize);

    /**
     * 查询操作
     *
     * @param nativeSearchQueryBuilder 查询条件
     * @return
     */
    List<T> searchList(NativeSearchQueryBuilder nativeSearchQueryBuilder);

    /**
     * 查询操作-按照指定字段排序,多个字段按照先后顺序排序
     *
     * @param sortDTOList              排序条件
     * @param nativeSearchQueryBuilder 查询条件
     * @return
     */
    List<T> searchListBySort(List<SortDTO> sortDTOList, NativeSearchQueryBuilder nativeSearchQueryBuilder);

    /**
     * 获取PageRequest对象
     *
     * @param pageNumber 页数
     * @param pageSize   页码
     * @return
     */
    PageRequest getPageRequest(int pageNumber, int pageSize);

    /**
     * 用于分组查询
     *
     * @param nativeSearchQueryBuilder 查询条件
     * @return Aggregations 即 DSL 中的 aggs
     */
    Aggregations query(NativeSearchQueryBuilder nativeSearchQueryBuilder);
}

 

2、实现类 IBaseService:

package com.xinghua.elasticsearchservice.common.service.impl;

import com.xinghua.elasticsearchservice.common.dto.SortDTO;
import com.xinghua.elasticsearchservice.common.model.EntityEsModel;
import com.xinghua.elasticsearchservice.common.utils.CommonException;
import com.xinghua.elasticsearchservice.common.service.IBaseService;
import com.xinghua.elasticsearchservice.utils.DataUtils;
import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

/**
 * @Description 提供公共的,基础的 ElasticSearch 功能
 * @Author 姚广星
 * @Date 2020/2/27 22:50
 **/
@Slf4j
@Service
public class BaseServiceImpl<T extends EntityEsModel> implements IBaseService<T> {

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;


    @Override
    public String getIndexName() {
        return elasticsearchTemplate.getPersistentEntityFor(getEntityClass()).getIndexName();
    }

    @Override
    public String getIndexType() {
        return elasticsearchTemplate.getPersistentEntityFor(getEntityClass()).getIndexType();
    }

    @Override
    public Class<T> getEntityClass() {
        Class<T> tClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        log.info("The type of T : " + tClass);
        return tClass;
    }

    @Override
    public String delete(String id) {
        return elasticsearchTemplate.delete(getEntityClass(), id);
    }

    @Override
    public Boolean deleteIndex() {
        return elasticsearchTemplate.deleteIndex(getEntityClass());
    }

    @Override
    public void init(List<T> entityModelList) {
        //判断索引是否存在
        boolean indexExists = elasticsearchTemplate.indexExists(getEntityClass());
        if (indexExists) {
            //删除索引
            elasticsearchTemplate.deleteIndex(getEntityClass());
        }
        //创建索引和mapper
        this.createEntityEsIndex();
        //bulk 批量初始化数据
        this.batchInsertOrUpdate(entityModelList);
    }

    @Override
    public Boolean createEntityEsIndex() {
        boolean indexExists = elasticsearchTemplate.indexExists(getEntityClass());
        if (indexExists) {
            boolean deleteIndex = elasticsearchTemplate.deleteIndex(getEntityClass());
            if (!deleteIndex) {
                throw new CommonException(" delete entity elasticsearch index Error");
            }
        }
        return this.createProductEsIndexAndMappers();
    }

    @Override
    public void batchInsertOrUpdate(List<T> entityModelList) {
        if (CollectionUtils.isEmpty(entityModelList)) {
            throw new CommonException("entityModelList Can't be empty");
        }
        //判断索引是否存在 若不存在则创建索引和映射
        if (!elasticsearchTemplate.indexExists(getEntityClass())) {
            this.createEntityEsIndex();
        }

        List<IndexQuery> queries = new ArrayList<>();
        for (T entityEsModel : entityModelList) {
            IndexQuery indexQuery = new IndexQueryBuilder()
                    .withId(entityEsModel.getId())
                    .withObject(entityEsModel)
                    .build();
            queries.add(indexQuery);
        }
        //批量插入
        this.bulkInsert(queries);
    }

    @Override
    public String saveOrUpdate(T entityModel) {
        IndexQuery indexQuery = new IndexQueryBuilder()
                .withId(entityModel.getId())
                .withObject(entityModel)
                .build();
        return elasticsearchTemplate.index(indexQuery);
    }

    /**
     * 批量插入
     *
     * @param queries 需要更新的 IndexQuery 集合
     */
    private void bulkInsert(List<IndexQuery> queries) {
        //实际运用中一般是设置为 500 或者 1000
        int len = 1000;
        List<List<IndexQuery>> indexQueryLists = DataUtils.splitList(queries, len);
        indexQueryLists.stream().forEach(indexQueryList -> {
            elasticsearchTemplate.bulkIndex(indexQueryList);
        });

        elasticsearchTemplate.refresh(getEntityClass());
        log.info("bulkInsert completed of " + queries.size());
    }


    /**
     * 创建索引和映射
     */
    private Boolean createProductEsIndexAndMappers() {
        boolean index = elasticsearchTemplate.createIndex(getEntityClass());
        if (!index) {
            throw new CommonException(" create entity elasticsearch index error");
        }
        boolean putMapping = elasticsearchTemplate.putMapping(getEntityClass());
        if (!putMapping) {
            throw new CommonException(" create entity elasticsearch mappers error");
        }
        return true;
    }

    @Override
    public Page<T> searchPage(NativeSearchQueryBuilder nativeSearchQueryBuilder, int pageNumber, int pageSize) {
        nativeSearchQueryBuilder.withIndices(getIndexName());
        nativeSearchQueryBuilder.withTypes(getIndexType());
        nativeSearchQueryBuilder.withPageable(this.getPageRequest(pageNumber, pageSize));
        return elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), getEntityClass());
    }

    @Override
    public PageRequest getPageRequest(int pageNumber, int pageSize) {
        if (pageNumber <= 0) {
            throw new CommonException("The page number parameter cannot be less than or equal to 0");
        }
        //因为是从0开始计算的 所以此处需要自减1
        pageNumber--;
        return PageRequest.of(pageNumber, pageSize);
    }

    @Override
    public Page<T> searchPageBySort(List<SortDTO> sortDTOList, NativeSearchQueryBuilder nativeSearchQueryBuilder,
                                    int pageNumber, int pageSize) {
        this.joinSortBuilders(sortDTOList, nativeSearchQueryBuilder);
        return this.searchPage(nativeSearchQueryBuilder, pageNumber, pageSize);
    }

    @Override
    public List<T> searchList(NativeSearchQueryBuilder nativeSearchQueryBuilder) {
        nativeSearchQueryBuilder.withIndices(getIndexName());
        nativeSearchQueryBuilder.withTypes(getIndexType());
        return elasticsearchTemplate.queryForList(nativeSearchQueryBuilder.build(), getEntityClass());
    }

    @Override
    public List<T> searchListBySort(List<SortDTO> sortDTOList, NativeSearchQueryBuilder nativeSearchQueryBuilder) {
        this.joinSortBuilders(sortDTOList, nativeSearchQueryBuilder);
        return this.searchList(nativeSearchQueryBuilder);
    }

    /**
     * 拼接多个SortBuilders排序条件
     *
     * @param sortDTOList              排序条件集合
     * @param nativeSearchQueryBuilder 查询条件
     */
    private void joinSortBuilders(List<SortDTO> sortDTOList, NativeSearchQueryBuilder nativeSearchQueryBuilder) {
        for (SortDTO sortDTO : sortDTOList) {
            nativeSearchQueryBuilder.withSort(
                    SortBuilders.fieldSort(sortDTO.getKey()).order(sortDTO.getIsASC() == true ? SortOrder.ASC : SortOrder.DESC)
            );
        }
    }

    @Override
    public Aggregations query(NativeSearchQueryBuilder nativeSearchQueryBuilder) {
        nativeSearchQueryBuilder.withIndices(getIndexName());
        nativeSearchQueryBuilder.withTypes(getIndexType());
        return elasticsearchTemplate.query(nativeSearchQueryBuilder.build(), searchResponse -> searchResponse.getAggregations());
    }
}

3、基础测试类:BaseProductEsTest

package com.xinghua.elasticsearchservice;

import com.xinghua.elasticsearchservice.model.ProductEsModel;
import com.xinghua.elasticsearchservice.service.IProductEsService;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.List;

/**
 * @Description 基础功能测试类
 * @Author 姚广星
 * @Date 2020/3/1 12:55
 **/
@RunWith(SpringRunner.class)
@SpringBootTest
class BaseProductEsTest {

    @Autowired
    private IProductEsService productEsService;

    @BeforeEach
    void setUp() {
        //System.out.println("执行初始化");
    }

    @AfterEach
    void tearDown() {
    }

    /**
     * 获取索引名称
     */
    @Test
    void getIndexName() {
        System.out.println(productEsService.getIndexName());
    }

    /**
     * 获取类型
     */
    @Test
    void getIndexType() {
        System.out.println(productEsService.getIndexType());
    }

    /**
     * 删除索引
     */
    @Test
    void deleteIndex() {
        Boolean aBoolean = productEsService.deleteIndex();
        System.out.println("aBoolean = " + aBoolean);
    }

    /**
     * 返回泛型上的Class对象
     */
    @Test
    void getEntityClass() {
        System.out.println(productEsService.getEntityClass());
    }

    /**
     * 创建索引和映射(若原有索引存在则删除重新创建)
     */
    @Test
    void createEntityEsIndex() {
        System.out.println(productEsService.createEntityEsIndex());
    }

    /**
     * 初始化数据
     * 1: 删除原有的索引
     * 2: 创建索引并且初始化映射
     * 3: bulk 批量初始化数据
     */
    @Test
    void init() {
        List<ProductEsModel> productEsModels = new ArrayList<>();
        ProductEsModel p1 = new ProductEsModel("1", "小米2青春版手机", 1799.99,
                "广东省深圳市", "3", "小米", "小米2青春版手机");

        ProductEsModel p2 = new ProductEsModel("2", "小米2经典版手机", 1899.99,
                "广东省深圳市", "3", "小米", "小米2经典版手机");

        ProductEsModel p3 = new ProductEsModel("3", "小米note手机", 2799.99,
                "广东省深圳市", "3", "小米", "小米note手机");

        ProductEsModel p4 = new ProductEsModel("4", "华为手机1", 1999.99,
                "湖南省", "1", "华为", "华为手机1");

        ProductEsModel p5 = new ProductEsModel("5", "华为手机2", 2888.88,
                "湖南省", "1", "华为", "华为手机2");

        ProductEsModel p6 = new ProductEsModel("6", "vivo 10", 999.99,
                "广东省广州市", "2", "vivo", "vivo 10");

        ProductEsModel p7 = new ProductEsModel("7", "vivo 20", 1899.99,
                "广东省广州市", "2", "vivo", "vivo 20");

        productEsModels.add(p1);
        productEsModels.add(p2);
        productEsModels.add(p3);
        productEsModels.add(p4);
        productEsModels.add(p5);
        productEsModels.add(p6);
        productEsModels.add(p7);
        productEsService.init(productEsModels);
    }

    /**
     * 新增或修改
     */
    @Test
    void saveOrUpdate() {
        System.out.println(productEsService.saveOrUpdate(new ProductEsModel("8", "9", 1899.99,
                "广东省广州市", "2", "vivo", "80")));
    }

    /**
     * 删除
     */
    @Test
    void delete() {
        System.out.println(productEsService.delete("8"));
    }

    /**
     * 获取PageRequest对象
     */
    @Test
    void getPageRequest() {
        System.out.println(productEsService.getPageRequest(1, 10));
    }

    /**
     * 批量新增/更新
     */
    @Test
    void batchInsertOrUpdate() {
        List<ProductEsModel> productEsModels = new ArrayList<>();
        ProductEsModel p1 = new ProductEsModel("8", "罗技鼠标", 990.00,
                "广东省湛江市", "4", "罗技", "罗技鼠标");

        ProductEsModel p2 = new ProductEsModel("9", "双飞燕鼠标", 99.99,
                "广东省湛江市", "5", "双飞燕", "双飞燕鼠标");
        productEsModels.add(p1);
        productEsModels.add(p2);
        productEsService.batchInsertOrUpdate(productEsModels);
    }

}

4、各种高级查询功能测试类 ProductEsTest

package com.xinghua.elasticsearchservice;

import com.xinghua.elasticsearchservice.common.dto.SortDTO;
import com.xinghua.elasticsearchservice.model.ProductEsModel;
import com.xinghua.elasticsearchservice.service.IProductEsService;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.DoubleTerms;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.aggregations.metrics.avg.InternalAvg;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.mockito.internal.invocation.MatchersBinder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @Description 各种高级查询功能测试类
 * @Author 姚广星
 * @Date 2020/3/1 12:55
 **/
@RunWith(SpringRunner.class)
@SpringBootTest
class ProductEsTest {

    @Autowired
    private IProductEsService productEsService;

    @BeforeEach
    void setUp() {
        //System.out.println("执行初始化");
    }

    @AfterEach
    void tearDown() {
    }

    /**
     * 分页查询-按照价格降序排列,显示第2页,每页显示3个   (分页+排序)
     */
    @Test
    void query1() {
        List<SortDTO> sortDTOList = new ArrayList<>();
        sortDTOList.add(new SortDTO("price", false));
        Page<ProductEsModel> productEsModels = productEsService.searchPageBySort(sortDTOList, new NativeSearchQueryBuilder(), 2, 3);
        productEsModels.forEach(System.out::println);
    }

    /**
     * 分页查询-按照品牌升序然后价格降序排列,显示第2页,每页显示3个     (分页+多条件排序)
     */
    @Test
    void query2() {
        List<SortDTO> sortDTOList = new ArrayList<>();
        sortDTOList.add(new SortDTO("brandId", true));
        sortDTOList.add(new SortDTO("price", false));
        Page<ProductEsModel> productEsModels = productEsService.searchPageBySort(sortDTOList, new NativeSearchQueryBuilder(), 2, 3);
        productEsModels.forEach(System.out::println);
    }

    /**
     * 查询商品标题中符合"小米 手机"的字样的商品   (match 查询)
     */
    @Test
    void query3() {
        NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();
        searchQueryBuilder.withQuery(
                QueryBuilders.matchQuery("title", "小米 手机")
        );
        List<ProductEsModel> productEsModels = productEsService.searchList(searchQueryBuilder);
        productEsModels.forEach(System.out::println);
    }

    /**
     * 查询所有商品标题中带有 鼠标 或者 价格为 1899.99,并且按照价格降序排序     (逻辑查询   must / should / must_not,相当于and / or / not)
     */
    @Test
    void query4() {
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        nativeSearchQueryBuilder.withQuery(
                QueryBuilders.boolQuery()
                        .should(QueryBuilders.termQuery("title", "鼠标"))
                        .should(QueryBuilders.termQuery("price", 1899.99))
        );
        List<SortDTO> sortDTOList = new ArrayList<>();
        sortDTOList.add(new SortDTO("price", false));
        List<ProductEsModel> productEsModels = productEsService.searchListBySort(sortDTOList, nativeSearchQueryBuilder);
        productEsModels.forEach(System.out::println);
    }

    /**
     * 查询商品标题或产地中符合"手机,湛江"的字样的商品    (关键字查询)
     */
    @Test
    void query5() {
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        nativeSearchQueryBuilder.withQuery(
                QueryBuilders.multiMatchQuery("手机,湛江", "origin", "title")
        );
        List<ProductEsModel> productEsModels = productEsService.searchList(nativeSearchQueryBuilder);
        productEsModels.forEach(System.out::println);
    }

    /**
     * 查询商品标题中符合"手机"的商品,并且价格在 1900~2800之间
     */
    @Test
    void query6() {
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        nativeSearchQueryBuilder.withQuery(
                QueryBuilders.boolQuery()
                        .must(QueryBuilders.termQuery("title", "手机"))
                        .must(QueryBuilders.rangeQuery("price").gte(1900).lte(2800))
        );
        List<ProductEsModel> productEsModels = productEsService.searchList(nativeSearchQueryBuilder);
        productEsModels.forEach(System.out::println);
    }

    /**
     * 分组查询(桶聚合)    按照品牌分组,统计各品牌的平均价格
     */
    @Test
    void query7() {
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        nativeSearchQueryBuilder.withIndices("product").withTypes("product");
        nativeSearchQueryBuilder.addAggregation(
                AggregationBuilders.terms("groupByBrandId").field("brandId")
                        .subAggregation(
                                AggregationBuilders.avg("avgPrice").field("price")
                        )
        );
        Aggregations group = productEsService.query(nativeSearchQueryBuilder);
        StringTerms groupByBrandId = group.get("groupByBrandId");
        List<StringTerms.Bucket> buckets = groupByBrandId.getBuckets();
        for (StringTerms.Bucket b : buckets) {
            InternalAvg avgPrice = b.getAggregations().get("avgPrice");
            System.out.println(avgPrice.getValue());
        }
    }

    @Test
    void query8() {

    }
}


原文链接:https://blog.csdn.net/a767815662/article/details/104705378

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值