04-Elasticsearch的 JestClient

04-Elasticsearch的 JestClient

1. 介绍

任何使用过Elasticsearch的人都知道,使用基于rest的搜索API构建查询可能是单调乏味且容易出错的。

在本教程中,我们将研究Jest,一个用于Elasticsearch的HTTP Java客户端。Elasticsearch提供了自己原生的Java客户端,然而 Jest提供了更流畅的API和更容易使用的接口

2. Maven依赖

<dependency>
    <groupId>io.searchbox</groupId>
    <artifactId>jest</artifactId>
    <version>6.3.1</version>
</dependency>

3. Jest配置

@Configuration
public class JestClientConfiguration {
    private static final Logger LOGGER = LoggerFactory.getLogger(JestClientConfiguration.class);

    @Value("${elasticsearch.cluster-name:my-application}")
    private String esClusterName;
    @Value("#{'${elasticsearch.cluster-nodes:127.0.0.1:9200}'.split(',')}")
    private List<String> serversList;


    public @Bean
    HttpClientConfig httpClientConfig() {
        HttpClientConfig httpClientConfig =  new HttpClientConfig
                .Builder(serversList.get(0))
                .gson(new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create())
                .multiThreaded(true)
                .readTimeout(1000000)
                .build();
        return httpClientConfig;
    }


    public @Bean
    JestClient jestClient() {
        JestClientFactory factory = new JestClientFactory();
        factory.setHttpClientConfig(httpClientConfig());
        LOGGER.info(">>[获得ES客户端连接:{}]", factory.getObject());
        return factory.getObject();
    }
}

4. 使用Jest

4.1 Jest常量

public final  class JestConstant {

    public static final String INDEX_TYPRE = "_doc";
}

4.2 索引枚举

@Slf4j
public enum JestEnum {

    JEST_CUST_INFO_INDEX("jest_cust_info","jest_cust", "机构", JestCustInfoItem.class),
    ;

    private String index; //索引名称
    private String alias; //索引别名
    private String text;  //备注 
    private Class<?> dtoClass; // 索引类Class

    JestEnum(String index, String alias, String text, Class dtoClass) {
        this.index = index;
        this.alias = alias;
        this.text = text;
        this.dtoClass = dtoClass;
    }


    public String getIndex() {
        return index;
    }

    //如果未设置别名则使用索引名称
    public String getAlias() {
        String aliasStr = alias;
        if (StringUtils.isBlank(aliasStr)) {
            aliasStr = index;
        }
        return aliasStr;
    }

    public Class<?> getDtoClass() {
        return dtoClass;
    }
	// 通过class反射获取索引的setting设置
    public String getSettings() {
        try {
            Object obj = dtoClass.newInstance();
            Method method = this.dtoClass.getMethod("getSettings");
            return (String) method.invoke(obj);
        } catch (NoSuchMethodException | IllegalAccessException
                | InvocationTargetException | InstantiationException e) {
            log.error("[反射调用异常, method: {}. class: {}]", "getSettings()",  this.dtoClass);
        }
        return "";
    }
	// 通过class反射获取索引的Mappings设置
    public String getMappings() {
        try {
            Object obj = dtoClass.newInstance();
            Method method  = this.dtoClass.getMethod("getMappings");
            return (String) method.invoke(obj);
        } catch (NoSuchMethodException | IllegalAccessException
                | InvocationTargetException | InstantiationException e) {
            log.error("[反射调用异常, method: {}. class: {}]", "getMappings()",  this.dtoClass);
            e.printStackTrace();
        }
        return "";
    }
    // 通过class反射获取索引类型
    public String getIndexType() {
        try {
            Object obj = dtoClass.newInstance();
            Method method  = this.dtoClass.getMethod("getIndexType");
            return (String) method.invoke(obj);
        } catch (NoSuchMethodException | IllegalAccessException
                | InvocationTargetException | InstantiationException e) {
            log.error("[反射调用异常, method: {}. class: {}]", "getMappings()",  this.dtoClass);
            e.printStackTrace();
        }
        return "";
    }

    public String getText() {
        return text;
    }
}

4.3 创建索引类

先创建父类

@Slf4j
public abstract class AbstractJestItem {

    @ApiModelProperty("总记录数")
    private Long totalHis;

    @ApiModelProperty("得分")
    private Double itemScore;

    public Long getTotalHis() {
        return totalHis;
    }

    public void setTotalHis(Long totalHis) {
        this.totalHis = totalHis;
    }

    public Double getItemScore() {
        return itemScore;
    }

    public void setItemScore(Double itemScore) {
        this.itemScore = itemScore;
    }

    @JsonIgnore
    public abstract String getMappings();

    @JsonIgnore
    public String getIndexType() {
        return JestConstant.INDEX_TYPRE;
    }

    @JsonIgnore
    public String getSettings() {
        try {
            XContentBuilder builder = jsonBuilder()
                    .startObject()
                        .field("number_of_shards", 3)
                        .field("number_of_replicas", 1)
                    .endObject();
            return Strings.toString(builder);
        } catch (Exception e) {
            log.error("[JSON 转化异常, method:{}. class:{}]", "getSettings()",  this.getClass());
        }
        return "";
    }
}

创建索引类

@Slf4j
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class JestCustInfoItem extends AbstractJestItem {
    @ApiModelProperty("客户ID")
    private String custId;

    @ApiModelProperty("客户名称")
    private String custName;

    @Override
    @JsonIgnore
    public String getMappings() {
        try {
        XContentBuilder builder = jsonBuilder()
                .startObject()
                    .startObject(getIndexType())
                        .startObject("properties")
                            .startObject("custId")
                                .field("type", "keyword")
                                .field("index", true)
                            .endObject()
                            .startObject("custName")
                                .field("type", "text")
                                .field("index", true)
                                .field("analyzer", "ik_max_word")
                                .field("search_analyzer", "ik_smart")
                            .endObject()
                        .endObject()
                    .endObject()
                .endObject();
            return Strings.toString(builder);
        } catch (Exception e) {
            log.error("[JSON 转化异常, method:{}. class:{}]", "getMappings()",  this.getClass());
        }
        return "";
    }
}

4.4 索引工具类

/**
 * Elasticsearch处理工具类
 * 采用Jest客户端
 */
public class JestUtils {
    private static final Logger LOGGER = LoggerFactory.getLogger(JestUtils.class);
    private static final JestClient jestClient = SpringContextUtils.getBean(JestClient.class);
    private static final Integer BATCH_NUM = 1000;

    /**
     * 验证索引是否存在
     * @param jestEnum  索引枚举
     * @return true: 索引存在, false:索引不存在
     */
    public static Boolean existsIndex(JestEnum jestEnum) {
        try {
            JestResult jr = jestClient.execute(new IndicesExists.Builder(jestEnum.getIndex()).build());
            return null != jr && jr.isSucceeded();
        } catch (IOException e) {
            LOGGER.error("==> 验证索引失败,indexName:{}, msg :{}", jestEnum.getIndex(), e);
            return false;
        }
    }

    /**
     * 创建ES索引和mapping
     * @param jestEnum 索引枚举
     * @return true: 成功,false: 失败
     */
    public static Boolean createIndex(JestEnum jestEnum) {
        try {
            JestResult jr = jestClient.execute(new CreateIndex.Builder(jestEnum.getIndex())
                    .settings(jestEnum.getSettings())
                    .mappings(jestEnum.getMappings())
                    .build());
            return null != jr && jr.isSucceeded();
        } catch (IOException e) {
            LOGGER.error("==> 创建索引失败,indexName:{}, msg :{}", jestEnum.getIndex(), e);
           e.printStackTrace();
        }
        return false;
    }

    /**
     * 删除索引
     */
    public static Boolean deleteIndex(JestEnum jestEnum) {
        try {
            JestResult jr = jestClient.execute(new DeleteIndex.Builder(jestEnum.getIndex()).build());
            return null != jr && jr.isSucceeded();
        } catch (IOException e) {
            LOGGER.error("==> 删除索引失败,indexName:{}, msg :{}", jestEnum.getIndex(), e);
            return false;
        }
    }
    /**
     * 索引不存在则创建索引
     */
    public static boolean createIndexAndMapping(JestEnum jestEnum) {
        return  !existsIndex(jestEnum) &&  createIndex(jestEnum);
    }
    /**
     * 索引存在则删除索引
     */
    public static boolean deleteIndexAndMapping(JestEnum jestEnum) {
        return  existsIndex(jestEnum) &&  deleteIndex(jestEnum);
    }
    /**
     * 清理cache
     */
    public static void clearCache() {
        try {
            ClearCache clearCache = new ClearCache.Builder().build();
            jestClient.execute(clearCache);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 给索引添加别名
     */
    public static Boolean addAlias(JestEnum jestEnum) {
        try {
            AddAliasMapping build = new AddAliasMapping.Builder(jestEnum.getIndex(), jestEnum.getAlias()).build();
            JestResult jr = jestClient.execute(new ModifyAliases.Builder(build).build());
            return null != jr && jr.isSucceeded();
        } catch (IOException e) {
            LOGGER.error("==> 添加索引别名失败,indexName:{}, msg :{}", jestEnum.getIndex(), e);
            return false;
        }
    }
    /**
     * 删除索引别名
     */
    public static Boolean deleteAlias(JestEnum jestEnum) {
        try {
            RemoveAliasMapping build = new RemoveAliasMapping.Builder(jestEnum.getIndex(), jestEnum.getAlias()).build();
            JestResult jr = jestClient.execute(new ModifyAliases.Builder(build).build());
            return null != jr && jr.isSucceeded();
        } catch (IOException e) {
            LOGGER.error("==> 添加索引别名失败,indexName:{}, msg :{}", jestEnum.getIndex(), e);
            return false;
        }
    }

    public static void optimizeIndex() {
        Optimize optimize = new Optimize.Builder().build();
        jestClient.executeAsync(optimize, new JestResultHandler<JestResult>() {
            public void completed(JestResult jestResult) {
                LOGGER.error("==> optimizeIndex result:{}", jestResult.isSucceeded());
            }
            public void failed(Exception e) {
                e.printStackTrace();
            }
        });
    }

    /**
     * ES 单条插入书库并带主键
     */
    public static <T> Boolean insert(JestEnum jestEnum, T data, String uniqueId) {
        try {
            JestResult jr = jestClient.execute(new Index.Builder(data)
                    .index(jestEnum.getIndex())
                    .type(jestEnum.getIndexType())
                    .id(uniqueId)
                    .refresh(true)
                    .build());
            return null != jr && jr.isSucceeded();
        } catch (IOException e) {
            LOGGER.error("==> [保存文档异常],indexName:{}, msg :{}", jestEnum.getIndex(), e);
            return Boolean.FALSE;
        }
    }

    /**
     *单条插入数据
     */
    public static <T> Boolean insert(JestEnum jestEnum, T data) {
        try {
            JestResult jr = jestClient.execute(new Index.Builder(data)
                    .index(jestEnum.getIndex())
                    .type(jestEnum.getIndexType())
                    .refresh(true)
                    .build());
            return null != jr && jr.isSucceeded();
        } catch (IOException e) {
            LOGGER.error("==> [保存文档异常],indexName:{}, msg :{}", jestEnum.getIndex(), e);
            return Boolean.FALSE;
        }
    }

    /**
     * 批量插入数据
     */
    public static <T> void insert(JestEnum jestEnum, List<T> dataList) {
        if(CollectionUtils.isEmpty(dataList)) {
            throw new BizServiceException("批量保存"+jestEnum.getText()+"数据不能为空");
        }
        List<List<T>> itemLists = Lists.partition(dataList, BATCH_NUM);
        List<Index> actions = null;
        for (List<T> itemList : itemLists) {
            actions = new ArrayList<>();
            for(T item : itemList) {
                actions.add(new Index.Builder(item)
                        .id(UUIDUtil.create())
                        .build());
            }
            Bulk bulk = new Bulk.Builder()
                    .defaultIndex(jestEnum.getIndex())
                    .defaultType(jestEnum.getIndexType())
                    .addAction(actions)
                    .build();
            try {
                BulkResult result = jestClient.execute(bulk);
                if (null != result && result.isSucceeded()) {
                    try {
                        LOGGER.info("==>[批量保存" + jestEnum.getText() + "成功],条数 :{}", actions.size());
                        jestClient.execute(new Flush.Builder().build());
                    } catch (IOException e) {
                        LOGGER.info("==>[刷新索引失败],MSG :{}", e);
                    }
                }
            } catch (IOException e) {
                LOGGER.info("==>[批量保存" + jestEnum.getText() + "失败],条数 :{}", actions.size());
            }
        }
    }

    private static Map<String, Object> buildAggregation(JestAggreReqDto jestSearchDto, MetricAggregation aggregation) {
        // 处理统计
        Map<String, Object> aggreMap = new HashedMap();
        for (Map.Entry<String, String> entry : jestSearchDto.getAggreMap().entrySet()) {
            if (StringUtils.isNotBlank(entry.getKey())) {
                List<TermsAggregation.Entry> entryList = aggregation.getTermsAggregation(entry.getKey() + "_count").getBuckets();
                Map<String, Long> longMap = new HashedMap();
                for (TermsAggregation.Entry jobEntry : entryList) {
                    longMap.put(jobEntry.getKeyAsString(), jobEntry.getCount());
                }
                aggreMap.put(entry.getKey(), longMap);
            }
        }
        return aggreMap;
    }

    /**
     * ES 分页查询并做统计
     */
    public static JestAggreRespDto queryRecordAggre(JestAggreReqDto jestSearchDto, QueryBuilder queryBuilder,
                                                    HighlightBuilder highlightBuilder) {
        JestAggreRespDto result = new JestAggreRespDto();
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = (BoolQueryBuilder) queryBuilder;
        for (Map.Entry<String, String> entry : jestSearchDto.getAggreMap().entrySet()) {
            if (StringUtils.isNotBlank(entry.getKey())) {
                searchSourceBuilder.aggregation(AggregationBuilders.terms(entry.getKey() + "_count").field(entry.getKey()));
            }
            if (StringUtils.isNotBlank(entry.getValue()) && !"ALL".equalsIgnoreCase(entry.getValue())) {
                boolQuery.must(QueryBuilders.matchPhraseQuery(entry.getKey(), entry.getValue()));
            }
        }
        Long formLong = (jestSearchDto.getPageNum() - 1) * jestSearchDto.getPageSize();
        searchSourceBuilder.query(boolQuery)
                .highlighter(highlightBuilder)
                .from(formLong.intValue())
                .size(jestSearchDto.getPageSize().intValue());
        Search search =  new Search.Builder(searchSourceBuilder.toString())
                .addIndex(jestSearchDto.getJestEnum().getIndex())
                .addType(jestSearchDto.getJestEnum().getIndexType())
                .addSort(new Sort("_score", Sort.Sorting.DESC))
                .build();
        List<AbstractJestItem> dataList = new ArrayList<>();
        try {
            SearchResult searchResult = jestClient.execute(search);
            if (searchResult != null && searchResult.isSucceeded()) {
                Long totalHis = searchResult.getTotal();
                // 封装数据
                List<? extends SearchResult.Hit<?, Void>> hits = searchResult.getHits(jestSearchDto.getJestEnum().getDtoClass());
                for (SearchResult.Hit<?, Void> hit : hits) {
                    AbstractJestItem source = (AbstractJestItem) hit.source;
                    // 处理高亮
                    Map<String, List<String>> highlight = hit.highlight;
                    for (HighlightBuilder.Field f : highlightBuilder.fields()) {
                        if ( f.name() != null) {
                            Field field = jestSearchDto.getJestEnum().getDtoClass().getDeclaredField(f.name());
                            field.setAccessible(true);
                            field.set(source, highlight.get(f.name()).get(0).toString());
                        }
                    }
                    source.setTotalHis(totalHis);
                    source.setItemScore(hit.score);
                    dataList.add(source);
                }
                //处理统计
                if ( null != searchResult.getAggregations()) {
                    result.setAggrMap(buildAggregation(jestSearchDto, searchResult.getAggregations()));
                }
                result.setDataList(dataList);
                result.setTotalHis(totalHis);
            } else {
                result.setDataList(dataList);
                result.setAggrMap(null);
                result.setTotalHis(0L);
            }
        } catch (IOException | NoSuchFieldException | IllegalAccessException  e) {
            LOGGER.error("[反射调用异常, method: {}. class: {}]", "getSettings()");
            e.printStackTrace();
        }
        return result;
    }

    /**
     * ES 分页查询关键字
     */
    public static PageInfo<AbstractJestItem> queryRecords(AbstractSearchDto jestSearchDto, QueryBuilder queryBuilder,
                                                          HighlightBuilder highlightBuilder) {
        PageInfo<AbstractJestItem> pageInfo = new PageInfo<AbstractJestItem>();
        Long formLong = (jestSearchDto.getPageNum() - 1) * jestSearchDto.getPageSize();
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
                .query(queryBuilder)
                .highlighter(highlightBuilder)
                .from(formLong.intValue())
                .size(jestSearchDto.getPageSize().intValue());
        Search search =  new Search.Builder(searchSourceBuilder.toString())
                .addIndex(jestSearchDto.getJestEnum().getIndex())
                .addType(jestSearchDto.getJestEnum().getIndexType())
                .addSort(new Sort("_score", Sort.Sorting.DESC))
                .build();
        List<AbstractJestItem> custInfoDtoList = new ArrayList<>();
        try {
            SearchResult searchResult = jestClient.execute(search);
            if (searchResult != null && searchResult.isSucceeded()) {
                Long totalHis = searchResult.getTotal();
                List<? extends SearchResult.Hit<?, Void>> hits = searchResult.getHits(jestSearchDto.getJestEnum().getDtoClass());
                for (SearchResult.Hit<?, Void> hit : hits) {
                    AbstractJestItem source = (AbstractJestItem) hit.source;
                    Map<String, List<String>> highlight = hit.highlight;
                    for (HighlightBuilder.Field f : highlightBuilder.fields()) {
                        if ( f.name() != null) {
                            Field field = jestSearchDto.getJestEnum().getDtoClass().getDeclaredField(f.name());
                            field.setAccessible(true);
                            field.set(source, highlight.get(f.name()).get(0).toString());
                        }
                    }
                    source.setTotalHis(totalHis);
                    source.setItemScore(hit.score);
                    custInfoDtoList.add(source);
                }
                pageInfo.setDataList(custInfoDtoList);
                pageInfo.setPageNum(jestSearchDto.getPageNum());
                pageInfo.setPageSize(jestSearchDto.getPageSize());
                pageInfo.setTotalNum(totalHis);
                pageInfo.setTotalSize((pageInfo.getTotalNum() / pageInfo.getPageSize()) + 1);
            }else {
                pageInfo.setDataList(custInfoDtoList);
                pageInfo.setPageNum(0L);
                pageInfo.setPageSize(jestSearchDto.getPageSize());
                pageInfo.setTotalNum(0L);
                pageInfo.setTotalSize((pageInfo.getTotalNum() / pageInfo.getPageSize()) + 1);
            }
        } catch (IOException | NoSuchFieldException | IllegalAccessException  e) {
            LOGGER.error("[反射调用异常, method: {}. class: {}]", "getSettings()");
            e.printStackTrace();
        }
        return pageInfo;
    }

    private static String upperFirstLatter(String letter){
        char[] chars = letter.toCharArray();
        if(chars[0]>='a' && chars[0]<='z'){
            chars[0] = (char) (chars[0]-32);
        }
        return new String(chars);
    }
}

4.5 测试

public class JestTest extends BaseTest   {
    private static final Logger LOGGER = LoggerFactory.getLogger(JestTest.class);
    private static final ObjectMapper MAPPER = new ObjectMapper();
    @Test
    public void create() throws Exception {
        Boolean flag = JestUtils.createIndexAndMapping(JestEnum.JEST_CUST_INFO_INDEX);
        LOGGER.info("[创建索引,flag: {}]", flag);
    }

    @Test
    public void delete() throws Exception {
        Boolean flag = JestUtils.deleteIndexAndMapping(JestEnum.JEST_CUST_INFO_INDEX);
        LOGGER.info("[删除索引,flag: {}]", flag);
    }

    @Test
    public void addAlias() throws Exception {
        Boolean flag = JestUtils.addAlias(JestEnum.JEST_CUST_INFO_INDEX);
        LOGGER.info("[添加索引别名,flag: {}]", flag);
    }

    @Test
    public void deleteAlias() throws Exception {
        Boolean flag = JestUtils.deleteAlias(JestEnum.JEST_CUST_INFO_INDEX);
        LOGGER.info("[删除索引别名,flag: {}]", flag);
    }

    @Test
    public void optimizeIndex() throws Exception {
        JestUtils.optimizeIndex();
    }


    @Test
    public void saveBatchCustInfo() {
        List<CustInfoDbDto> custDbList = custInfoMapper.getCustInfo();
        if(CollectionUtils.isEmpty(custDbList)) {
            LOGGER.info("==>[批量保存企业机构信息,无数据]");
            return ;
        }
        List<CustInfoItem> custInfoItemList = new ArrayList<CustInfoItem>();
        JestUtils.insert(JestEnum.JEST_CUST_INFO_INDEX, custInfoItemList);
    }
    @Test
    public void queryRecords() throws Exception {
        AbstractSearchDto jestSearchDto = new AbstractSearchDto();
        jestSearchDto.setJestEnum(JestEnum.JEST_CUST_INFO_INDEX);
        jestSearchDto.setKeyword("汽车");

        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.must(QueryBuilders.matchQuery("custName", jestSearchDto.getKeyword()));

        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<em>");
        highlightBuilder.postTags("</em>");
        highlightBuilder.field("custName");
        PageInfo<AbstractJestItem> pageInfo = JestUtils.queryRecords(jestSearchDto, boolQuery, highlightBuilder);
        LOGGER.info("-> {}", MAPPER.writeValueAsString(pageInfo));
    }

    @Test
    public void queryRecordAggre() throws Exception {
        JestAggreReqDto jestSearchDto = new JestAggreReqDto();
        jestSearchDto.setJestEnum(JestEnum.JEST_CUST_INFO_INDEX);
        jestSearchDto.setKeyword("汽车");

        Map<String, String> aggreMap = new HashedMap();
        aggreMap.put("province", "广东省");
        aggreMap.put("industryTypeName", null);
        aggreMap.put("levelName", null);
        jestSearchDto.setAggreMap(aggreMap);

        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.must(QueryBuilders.matchQuery("custName", jestSearchDto.getKeyword()));

        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<em>");
        highlightBuilder.postTags("</em>");
        highlightBuilder.field("custName");
        JestAggreRespDto jestAggreCustInfo = JestUtils.queryRecordAggre(jestSearchDto, boolQuery, highlightBuilder);
        LOGGER.info("-> {}", MAPPER.writeValueAsString(jestAggreCustInfo));
    }
}

源码下载:https://download.csdn.net/download/yk10010/12094309

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值