Java操作Elaticsearch

637 阅读5分钟

Java操作ElasticSearch有两种方式:

1)ElasticSearch提供的客户端API

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>transport</artifactId>
    <version>7.6.2</version>
</dependency>

2)Spring Data ElasticSearch

使用Spring Data 下二级子项目Spring Data Elasticsearch进行操作。支持POJO方法操作Elasticsearch。相比Elasticsearch提供的API更加简单更加方便。

在这里我们使用SpringData

1、SpringData ElasticSearch 项目环境搭建

1-1 创建项目

1-2 修改pom.xml

使用spring-boot-starter-parent版本为2.3.3.RELEASE,对应spring-data-elasticsearch版本为4.0.3

在项目pom.xml中添加依赖

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

1-3 修改配置文件

在老版本中通过9300内部端口访问。通过TransportClient进行访问的。

从Elasticsearch 8.x开始要放弃Transport。

所以从Spring Data Elasticsearch 4.0 开始都是基于Rest进行访问。

spring:
  elasticsearch:
    rest:
      uris: http://192.168.163.12:9200

1-4 新建索引和指定mapping

package com.alan.es.pojo;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;


/**
 * 商品实体类
 * 自定义mapping关系是通过实体类进行控制的
 * @Document 注解 org.springframework.data.elasticsearch.annotations
 * 定义类和索引的关系
 */
@Document(indexName = "index_item",shards = 1,replicas = 1)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Item {
    /**
     * 在SpringData ES中要求所有的属性都必须要有对应注解,否则自动映射
     */
    @Id
    //ID起名“id”和“_id” 都可以
    private String id;
    /*
    非主键都用Field注解
        如果希望Document和实体类中都叫做 title,那么 @Field(value = "")
        value不用填值,如果希望名字不一样,那就需要填值
     */
    @Field(type = FieldType.Text,analyzer = "ik_max_word")
    private String title;
    @Field(type = FieldType.Long)
    private Long price;
    @Field(type = FieldType.Keyword)
    private String catName;
}
@Document指定实体类和索引对应关系
    indexName:索引名称
    type: 索引类型(从ES 7.0 开始已经过时了)
    shards: 主分片数量。从ES 7开始默认1
    replicas:复制分片数量。从ES 7开始默认1
@Id 指定主键
@Field指定普通属性
    type: 对应Elasticsearch中属性类型。使用FiledType枚举可以快速获取。测试发现没有type属性可能出现无法自动创建类型问题,所以一定要有type属性。
        text类型能被分词
        keywords不能被分词
    index: 是否创建索引。作为搜索条件时index必须为true
    analyzer:指定分词器类型。

2、 ElasticsearchTemplate的使用

2-1 初始化索引

@SpringBootTest
class EsDemoApplicationTests {

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Test
    void contextLoads() {
        //调用代码,实现映射关系
        // SpringData 4.x操作索引都是通过IndexOptions进行操作
        IndexOperations operations = elasticsearchRestTemplate.indexOps(Item.class);
        //创建索引
        operations.create();
        //createMapping 根据实体类获取映射关系
        //putMapping 把映射关系添加到索引中
        Document mapping = operations.createMapping();
        boolean b = operations.putMapping(mapping);

        System.out.println("索引创建:" + b);
    }

}

2-2 删除索引

/**
 * 删除索引
 */
@Test
void deleteIndex(){
    IndexOperations operations = elasticsearchRestTemplate.indexOps(Item.class);
    operations.delete();
}

2-3 添加文档

如果索引和类型不存在,也可以执行新增,新增后自动创建索引和类型。但是field通过动态mapping进行映射,ElasticSearch根据值类型进行判断每个属性类型,默认每个属性都是standard分词器,ik分词器是不生效的。所以一定要先通过diamante进行初始化或者直接通过命令创建所有field的mapping。

2-3-1 新增单个文档

如果对象的id属性没有赋值,让ES自动生成主键,存储时ID属性没有值,_Id存储document的主键值。

如果存储对象的id属性明确设置了值,存储时id属性为设置的值,ES中document对象的_id也是设置的值。

/**
 * 新增单个文档
 */
@Test
void addDocument(){
    Item item = new Item("id", "标题", 1110000l, "catName");
    Item save = elasticsearchRestTemplate.save(item);
    System.out.println("新增单个文档:"+save);
}

/**
 * 新增单个文档(不指定ID)
 */
@Test
void addDocument(){
    Item item = new Item(null, "标题", 1110000l, "catName");
    Item save = elasticsearchRestTemplate.save(item);
    System.out.println("新增单个文档:"+save);
}
//输出:新增单个文档:Item{id='BiAkpIAB5cT0lDi1vRvj', title='标题', price=1110000, catName='catName'}

2-3-2 批量新增

/**
 * 批量新增文档
 */
@Test
void addDocuments(){
    List<Item> list = new ArrayList<>();
    list.add(new Item("id0000002","标题2",12l,"catName2"));
    list.add(new Item("id0000003","标题3",13l,"catName3"));
    list.add(new Item("id0000004","华为手机",4999l,"华为手机!功能牛逼!"));
    list.add(new Item("id0000005","小米手机",4999l,"小米手机,为发烧而生!"));
    Iterable<Item> save = elasticsearchRestTemplate.save(list);
    System.out.println("批量新增文档:"+save);
}

2-4 删除文档

/**
 * 删除文档
 */
@Test
void deleteDocument(){
    String id = elasticsearchRestTemplate.delete("id", Item.class);
    System.out.println("删除文档:"+id);
}

2-5 修改文档

修改的API就是新增的API,只要保证主键ID是已存在的ID,那么新增就是修改

2-6 查询

2.6-1 根据主键查询

/**
 * 根据主键查询
 */
@Test
void searchById(){
    Item res = elasticsearchRestTemplate.get("id0000002", Item.class);
    System.out.println("res:"+res);
}

2-6-2 模糊查询

/**
 * 模糊查询
 * 去所有field中查询指定条件。
 */
@Test
void search(){
    QueryStringQueryBuilder queryBuilder = QueryBuilders.queryStringQuery("标题");
    Query query = new NativeSearchQuery(queryBuilder);
    // 相当于最外层hits
    SearchHits<Item> searchHits = elasticsearchRestTemplate.search(query,Item.class);
    // 里层的hits
    List<SearchHit<Item>> list = searchHits.getSearchHits();
    // 进行转换
    List<Item> listResult = new ArrayList<>();
    list.forEach(sh -> {
        listResult.add(sh.getContent());
    });
    System.out.println(listResult);
}

2-6-3 使用match_all 查询所有文档

/**
 * 使用match_all 查询所有文档
 */
@Test
void matchAllSearch(){
    Query query = new NativeSearchQuery(QueryBuilders.matchAllQuery());

    SearchHits<Item> searchHits = elasticsearchRestTemplate.search(query, Item.class);

    List<Item> listRes = new ArrayList<>();

    searchHits.forEach(sh->{
        listRes.add(sh.getContent());
    });
    System.out.println("res:"+listRes);
}

2-6-4 使用match查询文档

/**
 * 使用match 查询文档
 */
@Test
void matchSearch(){
    Query query = new NativeSearchQuery(QueryBuilders.matchQuery("title","华为"));
    SearchHits<Item> searchHits = elasticsearchRestTemplate.search(query, Item.class);

    List<SearchHit<Item>> searchHitList = searchHits.getSearchHits();


    List<Item> res = new ArrayList<>();
    searchHitList.forEach(sh->{
        res.add(sh.getContent());
    });

    System.out.println("res:"+res);
}

2-6-5 使用 match_phrase 查询文档

短语搜索是对条件不分词,但是文档中属性根据配置实体类时指定的分词类型进行分词。

如果属性使用ik分词器,从分词后的索引数据进行匹配。

/**
     * 短语搜索是对条件不分词,但是文档中属性根据配置实体类时指定的分词类型进行分词。
     * 如果属性使用ik分词器,从分词后的索引数据进行匹配。
     */
    @Test
    void matchPhrase(){
        Query searchQuery = new NativeSearchQuery(QueryBuilders.matchPhraseQuery("title","手机"));
        SearchHits<Item> searchHits = elasticsearchRestTemplate.search(searchQuery, Item.class);
        List<SearchHit<Item>> searchHitList = searchHits.getSearchHits();


        List<Item> res = new ArrayList<>();
        searchHitList.forEach(sh->{
            res.add(sh.getContent());
        });

        System.out.println("res:"+res);
    }

2-6-6 使用range查询文档

@Test
    void range(){
        Query searchQuery = new NativeSearchQuery(QueryBuilders.rangeQuery("price").gte(100).lte(5000));
        SearchHits<Item> searchHits = elasticsearchRestTemplate.search(searchQuery, Item.class);

        List<SearchHit<Item>> searchHitList = searchHits.getSearchHits();

        List<Item> res = new ArrayList<>();
        searchHitList.forEach(sh->{
            res.add(sh.getContent());
        });

        System.out.println("res:"+res);
    }

2-6-7 多条件查询

@Test
void mustShould(){
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    List<QueryBuilder> listQuery = new ArrayList<>();

    listQuery.add(QueryBuilders.matchPhraseQuery("title","手机"));
    listQuery.add(QueryBuilders.rangeQuery("price").gte(1000).lte(6000));
//        boolQueryBuilder.should().addAll(listQuery);// ||
    boolQueryBuilder.must().addAll(listQuery);// &&
    Query query = new NativeSearchQuery(boolQueryBuilder);
    SearchHits<Item> search = elasticsearchRestTemplate.search(query, Item.class);

    List<Item> res = new ArrayList<>();
    for (SearchHit<Item> searchHit : search.getSearchHits()) {
        res.add(searchHit.getContent());
    }

    System.out.println("res:"+res);
}

2-6-8 分页与排序

/**
 * 分页与排序
 */
@Test
void PageSort(){
    Query query = new NativeSearchQuery(QueryBuilders.matchAllQuery());

    query.setPageable(PageRequest.of(0,2));

    query.addSort(Sort.by(Sort.Direction.DESC,"price"));
    SearchHits<Item> search = elasticsearchRestTemplate.search(query, Item.class);

    for (SearchHit<Item> searchHit : search.getSearchHits()) {
        System.out.println("res:"+searchHit.getContent());
    }

}

如果实体类中主键只有@Id注解,String id对应ES中是text类型,text类型是不允许被排序,所以如果必须按照主键进行排序时需要在实体类中设置主键类型

2-6-9 高亮显示

@Test
void highLight(){
    NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(QueryBuilders.matchQuery("catName", "手机"));
    // 排序
    nativeSearchQuery.addSort(Sort.by(Sort.Direction.DESC,"price"));
    // 分页
    nativeSearchQuery.setPageable(PageRequest.of(0,2));
    // 设置高亮条件
    HighlightBuilder hlBuilder = new HighlightBuilder();
    // 哪个属性高亮
    hlBuilder.field("catName");
    // 高亮内容前缀
    hlBuilder.preTags("<span style='color:red'>");
    // 高亮内容后缀
    hlBuilder.postTags("</span>");
    // 高亮查询
    HighlightQuery hlQuery = new HighlightQuery(hlBuilder);
    // 应用高亮
    nativeSearchQuery.setHighlightQuery(hlQuery);

    // 外层hits
    SearchHits<Item> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Item.class);
    // 查询出的总条数
    System.out.println(searchHits.getTotalHits());
    // 里层hits
    List<SearchHit<Item>> searchHitList = searchHits.getSearchHits();

    List<Item> list = new ArrayList<>();

    searchHitList.forEach(sh ->{
        // 取出非高亮数据
        Item peo = sh.getContent();
        // 获取高亮数据
        String hlContent = sh.getHighlightField("catName").get(0);
        // 用高亮数据替换非高亮数据
        peo.setCatName(hlContent);
        list.add(peo);
    });

    System.out.println(list);
}

本文正在参加「技术专题19期 漫谈数据库技术」活动