Elasticsearch学习(六)手把手教你用Java操作Elaticsearch, 教你学会ElasticsearchTemplate的使用

2021-03-02 14:46:44 浏览数 (1)

Spring Data Elasticsearch

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

Spring Data Elasticsearch项目环境搭建

创建项目

以上项目是一个空项目,什么依赖都没有添加

添加依赖

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com</groupId>
    <artifactId>SpringDateEs</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>


    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>net.logstash.logback</groupId>
            <artifactId>logstash-logback-encoder</artifactId>
            <version>6.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

其中和es相关的就是一个依赖,以后在项目里面想要使用java操作es,那么就添加这个依赖就可以了

代码语言:javascript复制
    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>

编写配置文件

代码语言:javascript复制
spring:
  data:
    elasticsearch:
      cluster-name: docker-cluster
      cluster-nodes: 192.168.40.145:9300

配置文件中cluster-name的值如何而来?这个是es集群的名字 浏览器输入地址之后就出现了

cluster-nodes 的值就是es在哪个虚拟机上面,那么就是哪个虚拟机的ip地址,我们创建es容器的时候,有两个端口号,一个是9200,一个是9300,9200是浏览器访问es的端口号,9300是java项目访问es需要写的端口号,所以,在我们的项目里面,需要写的是9300

在测试类里面写测试的方法来操作es

代码语言:javascript复制
  @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

只要注入以上的这个,那么就可以使用elasticsearchTemplate操作es了

ElasticsearchTemplate的使用

1 创建实体

代码语言:javascript复制
@Document指定实体类和索引对应关系

indexName:索引名称    写了这个之后,有就用,没有就创建这个索引
type: 索引类型
shards: 主分片数量
replicas:复制分片数量

@Id 指定主键
@Field指定普通属性

type: 对应Elasticsearch中属性类型。使用FiledType枚举可以快速获取。测试发现没有type属性可能出现无法自动创建类型问题,所以一定要有type属性。
text类型能被分词
keywords不能被分词


index: 是否创建索引。作为搜索条件时index必须为true
analyzer:指定分词器类型。


@Document(indexName = "people_index",type = "people_type",shards = 1,replicas = 1)
public class People {
    @Id
    private String id;
    // 整个name不被分词,切不创建索引
    // Keyword表示不被分词
    @Field(type= FieldType.Keyword,index = false)
    private String name;
    // address被ik分词
    // Text类型的属性才能被分词
    @Field(type = FieldType.Text,analyzer = "ik_max_word")
    private String address;

    @Field(type = FieldType.Long,index = false)
    private int age;

2 初始化索引(相当于创建数据库)

根据实体类上面的注解写的东西,我们要创建索引,相当于我们要创建一个数据库,在测试类里面写:

代码语言:javascript复制
elasticsearchTemplate有很多的方法,其中createIndex()就是创建一个索引,只是创建一个数据库,里面的字段是什么类型,也就是在实体类里面的注解上面的那些字段的类型如何放到索引中,需要putMapping()方法。

也就是在创建索引的时候,createIndex(),putMapping()  都是要有的,只要写了这两个方法,那么在es里面就创建了一个索引了
代码语言:javascript复制
    @Test
    void contextLoads() {
//        根据实体类创建索引,
        boolean result1 = elasticsearchTemplate.createIndex(People.class);
        System.out.println(result1);
//        将索引放到软件里面
        boolean results = elasticsearchTemplate.putMapping(People.class);
        System.out.println(results);
    }

以上在实体里面的注解里面的索引的 名字是people_index_16 所以我们现在要创建的索引,执行代码

他们返回的值是

返回true,说明创建索引成功,我们可以在kibana里面查看

有这个索引,相当于有数据库了,只是里面没有数据

3 删除索引(删除数据库)

代码语言:javascript复制
@Test
void delete(){
    boolean result = elasticsearchTemplate.deleteIndex(People.class);
    System.out.println(result);
}

执行完上面的代码,我们在kibana里面查看索引在不在了?

相当于删除数据库了

4 添加文档 (往数据库添加数据)

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

4.1 新增单个文档(新增一条数据)

如果对象的id属性没有赋值,让ES自动生成主键,存储时id属性没有值,_id存储document的主键值。 如果对象的id属性明确设置值,存储时id属性为设置的值,ES中document对象的_id也是设置的值。

代码语言:javascript复制
@Test
void insert1(){

创建要往es中添加的数据的实体类
    People peo = new People();
    peo.setId("123");
    peo.setName("张三");
    peo.setAddress("北京市海淀区回龙观东大街");
    peo.setAge(18);



    IndexQuery query = new IndexQuery();
    query.setObject(peo);
    
index() 方法就是新增的方法,只是里面的参数的类型是IndexQuery,所以要创建IndexQuery对象

    String result = elasticsearchTemplate.index(query);
    System.out.println(result);
}

新增成功,返回的是数据的id值

我们从kibana里面看看有没有新增数据

4.2 批量新增(新增多条数据)

下面代码中使用的IndexQueryBuilder()进行构建,可以一行代码完成。也可以使用上面的IndexQuery()。效果是完全相同的,只是需要写多行。

代码语言:javascript复制
@Test
void bulk(){

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

    // IndexQuery多行写法
    IndexQuery indexQuery = new IndexQuery();
    indexQuery.setObject(new People("1","王五","北京东城",12));
    list.add(indexQuery);

    // IndexQuery 连缀写法
    list.add(new IndexQueryBuilder().withObject(new People("2","赵六","北京西城",13)).build());

    list.add(new IndexQueryBuilder().withObject(new People("3","吴七","北京昌平",14)).build());

    elasticsearchTemplate.bulkIndex(list);
}

执行完上面的代码,我们看看kibana里面

以上es里面确实有很多的数据了。

5 删除操作(删除数据)

根据主键删除

代码语言:javascript复制
delete(String indexName,String typeName,String id); 通过字符串指定索引,类型和id值
delete(Class,String id)
 第一个参数传递实体类类类型,建议使用此方法,
减少索引名和类型名由于手动编写出现错误的概率。

返回值为delete方法第二个参数值(删除文档的主键值)

代码语言:javascript复制
@Test
void deleteDoc(){
删除id为4的数据
    String result = elasticsearchTemplate.delete(People.class, "4");
    System.out.println(result);
}

按照条件删除

代码语言:javascript复制
DeleteQuery deleteQuery = new DeleteQuery();
deleteQuery.setQuery(new MatchQueryBuilder("desc","学生"));
elasticsearchTemplate.delete(deleteQuery,People.class);

6 修改操作(修改数据)

修改操作就是新增代码,只要保证主键id已经存在,新增就是修改

7 查询操作(查询数据)

7.1 模糊查询

去所有field中查询指定条件。

代码语言:javascript复制
@Test
void query(){
    // NativeSearchQuery构造方法参数。
    // 北京去和所有field进行匹配,只要出现了北京就可以进行查询
    QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery("北京");
    // 查询条件SearchQuery是接口,只能实例化实现类。
    SearchQuery searchQuery = new NativeSearchQuery(queryStringQueryBuilder);
    List<People> list = elasticsearchTemplate.queryForList(searchQuery, People.class);
    for(People people : list){
        System.out.println(people);
    }
}

7.2 使用match_all查询所有文档

代码语言:javascript复制
@Test
void matchAll(){
    SearchQuery searchQuery = new NativeSearchQuery(QueryBuilders.matchAllQuery());
    List<People> list = elasticsearchTemplate.queryForList(searchQuery, People.class);
    for(People people : list){
        System.out.println(people);
    }
}

7.3 使用match查询文档

代码语言:javascript复制
@Test
void match(){
    SearchQuery searchQuery = new NativeSearchQuery(QueryBuilders.matchQuery("address","我要去北京"));
    List<People> list = elasticsearchTemplate.queryForList(searchQuery, People.class);
    for(People people : list){
        System.out.println(people);
    }
}

7.4使用match_phrase查询文档

短语搜索是对条件不分词,但是文档中属性根据配置实体类时指定的分词类型进行分词。 如果属性使用ik分词器,从分词后的索引数据中进行匹配。

代码语言:javascript复制
@Test
void mathPhrase(){
    SearchQuery searchQuery = new NativeSearchQuery(QueryBuilders.matchPhraseQuery("address","北京市"));
    List<People> list = elasticsearchTemplate.queryForList(searchQuery, People.class);
    for(People people : list){
        System.out.println(people);
    }
}

7.5使用range查询文档

代码语言:javascript复制
@Test
void range(){
    SearchQuery searchQuery = new NativeSearchQuery(QueryBuilders.rangeQuery("age").gte(22).lte(23));
    List<People> list = elasticsearchTemplate.queryForList(searchQuery, People.class);
    for(People people : list){
        System.out.println(people);
    }
}

7.6多条件查询

代码语言:javascript复制
  @Test
    void MustShould(){
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        
        List<QueryBuilder> listQuery = new ArrayList<>();
        
        listQuery.add(QueryBuilders.matchPhraseQuery("name","张三"));
        listQuery.add(QueryBuilders.rangeQuery("age").gte(22).lte(23));
        
//        boolQueryBuilder.should().addAll(listQuery); // 逻辑或
        boolQueryBuilder.must().addAll(listQuery);  // 逻辑与
        
        SearchQuery searchQuery = new NativeSearchQuery(boolQueryBuilder);
        List<People> list = elasticsearchTemplate.queryForList(searchQuery, People.class);
        for(People people : list){
            System.out.println(people);
        }
    }

7.7分页与排序

代码语言:javascript复制
@Test
void PageSort(){
    SearchQuery searchQuery = new NativeSearchQuery(QueryBuilders.matchAllQuery());
    // 分页 第一个参数是页码,从0算起。第二个参数是每页显示的条数
    searchQuery.setPageable(PageRequest.of(0,2));
    // 排序 第一个参数排序规则 DESC ASC 第二个参数是排序属性
    searchQuery.addSort(Sort.by(Sort.Direction.DESC,"age"));
    List<People> list = elasticsearchTemplate.queryForList(searchQuery, People.class);
    for(People people : list){
        System.out.println(people);
    }
}

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

代码语言:javascript复制
@Id
@Field(type = FieldType.Keyword)
private String id;

7.8高亮查询

代码语言:javascript复制
    @Test
    void hl() {

创建高亮的实体类对象

        // 高亮属性
        HighlightBuilder.Field hf = new HighlightBuilder.Field("address");
        // 高亮前缀
        hf.preTags("<span>");
        // 高亮后缀
        hf.postTags("</span>");



创建查询对象
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                // 排序
                .withSort(SortBuilders.fieldSort("age").order(SortOrder.DESC))
                // 分页
                .withPageable(PageRequest.of(0, 2))
                // 应用高亮
                .withHighlightFields(hf)
                // 查询条件, 必须要有条件,否则高亮
                .withQuery(QueryBuilders.matchQuery("address", "北京市")).build();



第三个参数    new SearchResultMapper()   就是自定义映射的结果



AggregatedPage<People> peoples = elasticsearchTemplate.queryForPage(searchQuery, People.class,


 new SearchResultMapper() {
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
                /*
                {
  "took" : 190,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : 1.7918377,
    "hits" : [
      {
        "_index" : "people_index",
        "_type" : "people_type",
        "_id" : "1",
        "_score" : 1.7918377,
        "_source" : {
          "id" : "1",
          "name" : "张三",
          "address" : "北京市回龙观东大街",
          "age" : 21
        },
        "highlight" : {
          "address" : [
            "<span>北京</span><span>市</span>回龙观东大街"
          ]
        }
      },
      {
        "_index" : "people_index",
        "_type" : "people_type",
        "_id" : "2",
        "_score" : 1.463562,
        "_source" : {
          "id" : "2",
          "name" : "李四",
          "address" : "北京市大兴区科创十四街",
          "age" : 22
        },
        "highlight" : {
          "address" : [
            "<span>北京</span><span>市</span>大兴区科创十四街"
          ]
        }
      },
      {
        "_index" : "people_index",
        "_type" : "people_type",
        "_id" : "3",
        "_score" : 0.38845786,
        "_source" : {
          "id" : "3",
          "name" : "王五",
          "address" : "天津市北京大街",
          "age" : 23
        },
        "highlight" : {
          "address" : [
            "天津市<span>北京</span>大街"
          ]
        }
      }
    ]
  }
}

                 */

                // 取最里面层的hits
                SearchHit[] searchHits = searchResponse.getHits().getHits();
                // 最终返回的数据
                List<T> peopleList = new ArrayList<>();
                for (SearchHit searchHit : searchHits) {
                    //需要把SearchHit转换为People
                    People peo = new People();
                    // 取出非高亮数据
                    Map<String, Object> mapSource = searchHit.getSourceAsMap();
                    // 取出高亮数据
                    Map<String, HighlightField> mapHL = searchHit.getHighlightFields();
                    // 把非高亮数据进行填充
                    peo.setId(mapSource.get("id").toString());
                    peo.setName(mapSource.get("name").toString());
                    peo.setAge(Integer.parseInt(mapSource.get("id").toString()));
                    // 判断是否有高亮,如果只有一个搜索条件,一定有这个高亮数据,if可以省略
                    if (mapHL.containsKey("address")) {
                        // 设置高亮数据
                        peo.setAddress(mapHL.get("address").getFragments()[0].toString());
                    }
                    // 把people添加到集合中
                    peopleList.add((T) peo);
                }
                // 如果没有分页,只有第一个参数。
                // 总条数在第一个hits里面。
                return new AggregatedPageImpl<>(peopleList, pageable, searchResponse.getHits().totalHits);
            }

            @Override
            public <T> T mapSearchHit(SearchHit searchHit, Class<T> aClass) {
                return null;
            }
        });

        // 通过getContent查询出最终List<People>
        for (People people : peoples.getContent()) {
            System.out.println(people);
        }
    }

0 人点赞