ElasticsearchRestTemplate基础Java-Api调用

1,365 阅读4分钟

image.png

最近学了一下Elasticsearch、把网站的搜索功能实现了一下、然后准备把api的调用方式给小结一下。

创建一个SpringBoot项目

POM依赖

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-elasticsearch</artifactId>
</dependency>

开始使用

@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;

先创建实体类

/**
 *  ik_smart:进行最小粒度分词,ik_max_word进行最大粒度分词
 */
@Data
@Document(indexName = "xhs")
public class EsData {
    /**
     * 博客id
     */
    @Id
   private Long id;
    /**
     * 博客标题
     */
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
   private String blogTable;
    /**
     * 前端展示标题
     */
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
   private String blogWebContent;
    /**
     * 博客内容
     */
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String content;
    
    public static String getIndexName(){
        return EsData.class.getAnnotation(Document.class).indexName();
    }
}

@Document@Field注解是在执行Api的是可以做查询对应数据的对象映射、或者索引的创建、及文档数据的字段类型的限制

@Document

public @interface Document {
   /**
     *  索引的名称
    */
   String indexName();
   /**
    *   弃用的类型现在默认是_doc
    */
   @Deprecated
   String type() default "";

   /**
    * 是否使用这个注解的默认配置、false使用、true不使用
    */
   boolean useServerConfiguration() default false;
   /**
    * 分片数量
    */
   short shards() default 1;
   /**
    *  副本数量
    */
   short replicas() default 1;
   /**
    * 刷新间隔
    */
   String refreshInterval() default "1s";

   /**
    * 索引存储类型
    */
   String indexStoreType() default "fs";
   /**
    * 是否启动时创建索引、我做这个启动时不会自动创建、自行研究看源码
    */
   boolean createIndex() default true;
   /**
    *  版本号
    */
   VersionType versionType() default VersionType.EXTERNAL;
}

@Field

public @interface Field {

   /**
    * 别名
    */
   @AliasFor("name")
   String value() default "";

   /**
    * 文档中存储的名称、如果不设置别名使用属性名
    */
   @AliasFor("value")
   String name() default "";
   /**
    * 文档中存储的数据类型
    */
   FieldType type() default FieldType.Auto;
   /**
    * 是否能被搜索、true:可以作为搜索条件、false:不可以作为搜索条件
    */
   boolean index() default true;
   /**
    * 日期格式化 参考官方文档使用 https://www.elastic.co/guide/reference/mapping/date-format/
    */
   DateFormat format() default DateFormat.none;
   String pattern() default "";
   boolean store() default false;
   boolean fielddata() default false;
  /**
    *  搜索时使用对应使用的分词器
    */
   String searchAnalyzer() default "";
   /**
    *  使用对应使用的分词器
    */
   String analyzer() default "";
   String normalizer() default "";
   String[] ignoreFields() default {};
   boolean includeInParent() default false;
   String[] copyTo() default {};
   int ignoreAbove() default -1;
   boolean coerce() default true;
   boolean docValues() default true;
   boolean ignoreMalformed() default false;
   IndexOptions indexOptions() default IndexOptions.none;
   boolean indexPhrases() default false;
   IndexPrefixes[] indexPrefixes() default {};
   boolean norms() default true;
   String nullValue() default "";
   int positionIncrementGap() default -1;
   Similarity similarity() default Similarity.Default;
   TermVector termVector() default TermVector.none;
   double scalingFactor() default 1;
   int maxShingleSize() default -1;
}

以上没写注释的没去找具体那个地方使用、写的注释的基本都是常用的、上面说的一个问题是否会自动创建对应的索引、你可以使用另一种方式创建索引、并创建对应字段的映射类型Mapping

@PostConstruct
public void init() {
    try {
        // 判断索引是否存在
        boolean exists = elasticsearchRestTemplate.indexOps(EsBlogData.class).exists();
        if (!exists) {
            log.info("没有{}索引开始创建索引.....", EsBlogData.getIndexName());
            // 创建索引
            elasticsearchRestTemplate.indexOps(EsBlogData.class).create();
            // 创建映射关系
            elasticsearchRestTemplate.indexOps(EsBlogData.class)
                    .putMapping(elasticsearchRestTemplate.indexOps(EsBlogData.class).createMapping());
        }
    } catch (Exception e) {
        log.error("判断索引是否存在出现异常", e);
    }
}

单个

public Boolean insert(EsBlogData target) {
    EsBlogData save = elasticsearchRestTemplate.save(target);
    return true;
}

批量

public Integer batchInsert(List<EsBlogData> targetS) {
    List<IndexQuery> indexQueryList = new ArrayList<>();
    for (EsBlogData esBlogData : targetS) {
        if (null == esBlogData.getId()) continue;
        indexQueryList.add(new IndexQueryBuilder()
                .withId(String.valueOf(esBlogData.getId()))
                .withObject(esBlogData)
                .build()
        );
    }
    List<String> list = elasticsearchRestTemplate.bulkIndex(indexQueryList, elasticsearchRestTemplate.getIndexCoordinatesFor(EsBlogData.class));
    return indexQueryList.size();
}

单个

@Override
public Boolean delete(Object id) {
    String delete = elasticsearchRestTemplate.delete(String.valueOf(id), EsBlogData.class);
    return true;
}

多个(按条件)

@Override
public Integer batchDelete(List<Object> idS) {
    NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
            .withQuery(QueryBuilders.termsQuery("id", idS))
            .build();
    elasticsearchRestTemplate.delete(nativeSearchQuery, EsBlogData.class, elasticsearchRestTemplate.getIndexCoordinatesFor(EsBlogData.class));
    return 0;
}

条件删除构造的条件和查询一样、相当于就把能查询出来的数据删除。

单个(根据id)

@Override
public Boolean update(EsBlogData target) {
    UpdateQuery builder = UpdateQuery
            .builder(String.valueOf(target.getId()))
            .withDocument(Document.parse(JSONUtil.toJsonStr(target)))
            .build();
    UpdateResponse response = elasticsearchRestTemplate
            .update(builder, elasticsearchRestTemplate.getIndexCoordinatesFor(EsBlogData.class));
    return true;
}

要注意的就是如果es中修改已经存在的字段数据就是修改、如果es中没有这个字段的数据它会添加、如果需要修改单个字段的值的话对象的不需要修改可以不用设置值。

批量修改

@Override
public Integer batchUpdate(List<EsBlogData> targetS) {
    List<UpdateQuery> indexQueryList = new ArrayList<>();
    for (EsBlogData esBlogData : targetS) {
        if (null == esBlogData.getId()) continue;
        indexQueryList.add(UpdateQuery
                .builder(String.valueOf(esBlogData.getId()))
                .withDocument(Document.parse(JSONUtil.toJsonStr(esBlogData)))
                .build());
    }
    elasticsearchRestTemplate.bulkUpdate(indexQueryList, elasticsearchRestTemplate.getIndexCoordinatesFor(EsBlogData.class));
    return indexQueryList.size();
}

@Override
    public List<EsBlogData> search(SearchCondition content) {
        before(Operation.SELECT, content);

        if (ObjectUtil.isNull(content.getPageNo())) {
            content.setPageNo(1);
        }
        if (ObjectUtil.isNull(content.getPageSize())) {
            content.setPageSize(2);
        }

        if (StrUtil.isEmpty(content.getKeyWord())) {
            log.error("查询内容不能为空.....");
            return null;
        }

        if (CollUtil.isEmpty(content.getFieldS())) {
            log.error("查询的字段不能为空.....");
            return null;
        }

        // 构建高亮对象
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<span style='color:red'>");
        highlightBuilder.postTags("</span>");
        for (String field : content.getFieldS()) {
            // 设置高亮的字段名
            highlightBuilder.field(field);
        }

        // 构建查询对象
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 设置匹配的要的字段名
        boolQueryBuilder.must(QueryBuilders.multiMatchQuery(content.getKeyWord(), content.getFieldS().toArray(new String[]{})));

        // 是否有时间筛选
        if (ObjectUtil.isNotNull(content.getRecently())) {
            boolQueryBuilder.filter(QueryBuilders
                    .rangeQuery("createTime")
                    .format("yyyy-MM-dd HH:mm:ss")
                    .gt(content.getRecently().getRecently())
                    .lt(DateTime.now().toString()));
        }
        // 构建搜索对象
        NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
                .withQuery(boolQueryBuilder)
                .withHighlightBuilder(highlightBuilder)
                .withPageable(PageRequest.of(content.getPageNo() - 1, content.getPageSize()))
                .build();
            // 输出es dsl语句
//        System.out.println(nativeSearchQuery.getQuery().toString());
        SearchHits<EsBlogData> search = elasticsearchRestTemplate.search(nativeSearchQuery, EsBlogData.class);
        if (search.getTotalHits() == 0) {
            return null;
        }
        List<EsBlogData> list = new ArrayList<>();
        for (SearchHit<EsBlogData> hit : search.getSearchHits()) {
            for (String field : content.getFieldS()) {
                // 高亮后的数据
                List<String> highlightField = hit.getHighlightField(field);
                StringBuilder newContent = new StringBuilder();
                if (ObjectUtil.isNotNull(highlightField) && highlightField.size() > 0) {
                    newContent.append(highlightField.get(0));
                }
                try {
                    if (StrUtil.isNotBlank(newContent)) {
                        // 得到对应属性的操作对象
                        Field declaredField = hit.getContent().getClass().getDeclaredField(field);
                        declaredField.setAccessible(Boolean.TRUE);
                        // 将新值查询设置到对象中
                        declaredField.set(hit.getContent(), newContent.toString());
                    }

                } catch (NoSuchFieldException | IllegalAccessException e) {
                    log.error("高亮设置值错误", e);
                }
            }
            list.add(hit.getContent());
        }
        after(Operation.SELECT, search);
        return list;
    }

查询这一块我写多个字段中如果检索到我设置的keyWord搜索的值就将数据查出来

image.png

看下这个查询图、其实你会发现es的api还有有一定规律的、后续你不知道如何使用他的api的话可以去网上找接口调用时提供的json请求体中的数据一步一步的把对象给build出来作为查询使用。

每日一汤

快乐永远属于周五、何尝不把每一天当做周五!!! 快乐永远属于周五、何尝不把每一天当做周五!!!