一、 Java操作Elaticsearch

在Java中操作Elasticsearch有两种方式

1、 Elasticsearch提供的客户端API

依赖的名称叫做transport,rest-high-level。依赖的版本和Elasticsearch的版本是对应的。

  1. <dependency>
  2. <groupId>org.elasticsearch.client</groupId>
  3. <artifactId>transport</artifactId>
  4. <version>7.6.2</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.elasticsearch.client</groupId>
  8. <artifactId>elasticsearch-rest-high-level-client</artifactId>
  9. <version>7.6.2</version>
  10. </dependency>

2 、Spring Data Elasticsearch

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

二、 Spring Data Elasticsearch项目环境搭建

1、 创建项目

新建任意名称项目

2、 修改POM文件添加依赖

目前使用spring-boot-starter-parent版本为2.3.9RELEASE

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

3、 修改配置文件

在老版本中通过9300内部端口访问。通过TransportClient进行访问的。
从Elasticsearch 8.x开始要放弃Transport。
所以从Spring Data Elasticsearch 4.0 开始都是基于Rest进行访问。

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

三、 ElasticsearchTemplate的使用

1、创建实体

@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:指定分词器类型。

/**
 * indexName:索引的名称
 * shards:主分片的数量默认是1
 * replicas:副本分片的数量 默认是1
 * */
@NoArgsConstructor
@AllArgsConstructor
@Data
@Document(indexName = "stu_index",shards = 1,replicas = 0)
public class Student implements Serializable {
    @Id
    @Field(type = FieldType.Long,name = "id")
    private Long id;
    @Field(type = FieldType.Text,name = "name",analyzer = "ik_smart")
    private String name;
    @Field(type = FieldType.Integer,name = "age")
    private Integer age;
    @Field(type = FieldType.Keyword,name = "sex")
    private String sex;
    @Field(type = FieldType.Date,format = DateFormat.date,name = "birth")
    private Date birth;
    @Field(type = FieldType.Boolean,name = "isFlag")
    private Boolean isFlag;
    @Field(type = FieldType.Text,name = "hobby",analyzer = "ik_smart")
    private String[] hobby;
}

2、初始化索引

@SpringBootTest
class SpringdateEsApplicationTests {
    /**
     * ElasticsearchTemplate:底层基于transport  已经过时
     * ElasticsearchRestTemplate:底层基于rest-high-level
     */
    @Autowired
    private ElasticsearchRestTemplate esTemplate;
    //ES虽然提供了代码方式添加和删除索引 但是我们一般不使用,我们会在kibana中直接创建
    @Test
    void stuDemo(){
        //创建索引
        IndexOperations indexOperations = esTemplate.indexOps(Student.class);
        //创建当前索引
        indexOperations.create();
        //createMapping:根据指定对象创建Mapping映射
        //putMapping:给指定索引追加mapping关系
        indexOperations.putMapping(indexOperations.createMapping(Student.class));
    }
}

3、删除索引

@SpringBootTest
class SpringdateEsApplicationTests {
    /**
     * ElasticsearchTemplate:底层基于transport  已经过时
     * ElasticsearchRestTemplate:底层基于rest-high-level
     */
    @Autowired
    private ElasticsearchRestTemplate esTemplate;
    //ES虽然提供了代码方式添加和删除索引 但是我们一般不使用,我们会在kibana中直接创建

    @Test
    void stuDelDemo(){
        //删除指定的索引
        IndexOperations indexOperations = esTemplate.indexOps(Student.class);
        indexOperations.delete();
    }
}

4、添加文档

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

4.1 新增单个文档

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

@SpringBootTest
class SpringdateEsApplicationTests {
    /**
     * ElasticsearchTemplate:底层基于transport  已经过时
     * ElasticsearchRestTemplate:底层基于rest-high-level
     */
    @Autowired
    private ElasticsearchRestTemplate esTemplate;
    /*------------------------------ES文档的CURD操作--------------------------------------*/
    @Test
    void docAddDemo(){
        //如果当前ID属性没有指定ES中对应索引的_ID的值会自动生成,如果我们设置了ID这个时候_id就会使用自定义的id的值
        //如果ID相同,这个时候就是一个修改的操作 全量替换
        Student student = new Student();
        student.setId(1L);
        student.setName("郑明月");
        student.setAge(22);
        student.setSex("女性");
        student.setBirth(new Date());
        student.setIsFlag(false);
        student.setHobby(new String[]{"联盟","炉石"});
        Student stu1 = esTemplate.save(student);
        //添加文档
        System.out.println(stu1);
    }

4.2 批量新增

 @SpringBootTest
class SpringdateEsApplicationTests {
    /**
     * ElasticsearchTemplate:底层基于transport  已经过时
     * ElasticsearchRestTemplate:底层基于rest-high-level
     */
    @Autowired
    private ElasticsearchRestTemplate esTemplate;  
    @Test
    void docAddMore(){
        //批量添加文档
        Student student = new Student();
        student.setId(2L);
        student.setName("王旭");
        student.setAge(20);
        student.setSex("男性");
        student.setBirth(new Date());
        student.setIsFlag(true);
        student.setHobby(new String[]{"联盟","换装"});
        Student stu1 = esTemplate.save(student);
        Student student2 = new Student();
        student2.setId(3L);
        student2.setName("泰旭");
        student2.setAge(23);
        student2.setSex("男性");
        student2.setBirth(new Date());
        student2.setIsFlag(false);
        student2.setHobby(new String[]{"联盟","韩剧"});
        Student stu2 = esTemplate.save(student2);
        List<Student> list=new ArrayList<>();
        list.add(stu1);
        list.add(stu2);
        Iterable<Student> students = esTemplate.save(list);
        System.out.println(students);
    }
}

5、删除操作

@SpringBootTest
class SpringdateEsApplicationTests {
    /**
     * ElasticsearchTemplate:底层基于transport  已经过时
     * ElasticsearchRestTemplate:底层基于rest-high-level
     */
    @Autowired
    private ElasticsearchRestTemplate esTemplate;
    /*删除的实现*/
    @Test
    void docDeleteDemo(){
        //返回的是当前删除元素的ID
        String s = esTemplate.delete("3", Student.class);
        //esTemplate.delete("3", IndexCoordinates.of("stu_index"));
        System.out.println(s);
    }

}

6、修改操作

修改操作就是新增代码,只要保证主键id已经存在,新增就是修改
修改部分数据如下操作:

@SpringBootTest
class SpringdateEsApplicationTests {
    /**
     * ElasticsearchTemplate:底层基于transport  已经过时
     * ElasticsearchRestTemplate:底层基于rest-high-level
     */
    @Autowired
    private ElasticsearchRestTemplate esTemplate;
    /*
    * 修改操作:
    * 【A】:全量修改 save()操作
    * 【B】:部分修改 update()操作
    * */
    @Test
    void docUpdateDemo(){
        UpdateQuery query = UpdateQuery
                .builder("1")//修改索引的ID
                .withDocument(Document.parse("{\"age\":23,\"sex\":\"男\"}"))//修改的字段 如果有多个使用Json拼接
                .build();
        UpdateResponse a = esTemplate.update(query, IndexCoordinates.of("stu_index"));
        System.out.println(a.getResult());//执行结果的类型为Update
    }

}

7、查询操作

7.1 根据主键查询

@SpringBootTest
class SpringdateEsApplicationTests {
    /**
     * ElasticsearchTemplate:底层基于transport  已经过时
     * ElasticsearchRestTemplate:底层基于rest-high-level
     */
    @Autowired
    private ElasticsearchRestTemplate esTemplate;
    /*查询的实现*/
    @Test
    void docFindDemo(){
        //只可以根据ID查询
        Student student = esTemplate.get("1", Student.class);
        System.out.println(student);
    }

}

7.2 全字段模糊查询

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

@SpringBootTest
class SpringdateEsApplicationTests {
    /**
     * ElasticsearchTemplate:底层基于transport  已经过时
     * ElasticsearchRestTemplate:底层基于rest-high-level
     */
    @Autowired
    private ElasticsearchRestTemplate esTemplate;
    /*------------基于query String search进行查询-------------*/
    @Test
    void docFindDemo02(){
        //查询的条件
        Query query = new NativeSearchQuery(QueryBuilders.queryStringQuery("name:'旭'"));
        SearchHits<Student> search = esTemplate.search(query, Student.class);
/*        List<SearchHit<Student>> searchHits = search.getSearchHits();
        List<Student> list=new ArrayList<>();
        for (SearchHit<Student> searchHit : searchHits) {
            list.add(searchHit.getContent());
        }*/
        List<Student> list = QueryESUtil.QueryES(search);
        System.out.println(list);
    }

}

抽取其中集合添加的操作为com.bjsxt.util.QueryESUtil方法,如下

public class QueryESUtil {

    public static<T> List<T> QueryES(SearchHits<T> search){
        List<SearchHit<T>> searchHits = search.getSearchHits();
        List<T> list=new ArrayList<>();
        for (SearchHit<T> searchHit : searchHits) {
            list.add(searchHit.getContent());
        }
        return list;
    }
}

7.3 使用match_all查询所有文档

@SpringBootTest
class SpringdateEsApplicationTests {
    /**
     * ElasticsearchTemplate:底层基于transport  已经过时
     * ElasticsearchRestTemplate:底层基于rest-high-level
     */
    @Autowired
    private ElasticsearchRestTemplate esTemplate;   
    @Test
    void docSearchDemo(){
        //match_all查询所有文档
        Query query = new NativeSearchQuery(QueryBuilders.matchAllQuery());
        SearchHits<Student> search = esTemplate.search(query, Student.class);
        List<Student> list = QueryESUtil.QueryES(search);
        for (Student student : list) {
            System.out.println(student);
        }
    }
}

9.4 使用match查询文档

@SpringBootTest
class SpringdateEsApplicationTests {
    /**
     * ElasticsearchTemplate:底层基于transport  已经过时
     * ElasticsearchRestTemplate:底层基于rest-high-level
     */
    @Autowired
    private ElasticsearchRestTemplate esTemplate;
    @Test
    void docSearchDemo2(){
        //match具体匹配某一个数据
        Query query=new NativeSearchQuery(QueryBuilders.matchQuery("name","王旭"));
        SearchHits<Student> search = esTemplate.search(query, Student.class);
        List<Student> list = QueryESUtil.QueryES(search);
        for (Student student : list) {
            System.out.println(student);
        }
    }
}

7.5 使用match_phrase查询文档

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

@SpringBootTest
class SpringdateEsApplicationTests {
    /**
     * ElasticsearchTemplate:底层基于transport  已经过时
     * ElasticsearchRestTemplate:底层基于rest-high-level
     */
    @Autowired
    private ElasticsearchRestTemplate esTemplate;
    @Test
    void docSearchDemo3(){
        //match_phrase用户输入不拆分查询
        Query query=new NativeSearchQuery(QueryBuilders.matchPhraseQuery("name","王旭"));
        SearchHits<Student> search = esTemplate.search(query, Student.class);
        List<Student> list = QueryESUtil.QueryES(search);
        for (Student student : list) {
            System.out.println(student);
        }
    }
}

7.6 使用range查询文档

@SpringBootTest
class SpringdateEsApplicationTests {
    /**
     * ElasticsearchTemplate:底层基于transport  已经过时
     * ElasticsearchRestTemplate:底层基于rest-high-level
     */
    @Autowired
    private ElasticsearchRestTemplate esTemplate;
    @Test
    void docSearchDemo4(){
        //range范围查询
        Query query=new NativeSearchQuery(QueryBuilders.rangeQuery("age").gte(21).lte(30));
        SearchHits<Student> search = esTemplate.search(query, Student.class);
        List<Student> list = QueryESUtil.QueryES(search);
        for (Student student : list) {
            System.out.println(student);
        }
    }
}

7.7 多条件查询

@SpringBootTest
class SpringdateEsApplicationTests {
    /**
     * ElasticsearchTemplate:底层基于transport  已经过时
     * ElasticsearchRestTemplate:底层基于rest-high-level
     */
    @Autowired
    private ElasticsearchRestTemplate esTemplate;
    @Test
    void docSearchDemo5(){
        //多条件查询 查询姓名中含有旭并且年龄范围在21岁到25岁之间的数据
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //存放每一个条件
        List<QueryBuilder> listQuery = new ArrayList<>();
        listQuery.add(QueryBuilders.matchQuery("name","旭"));
        listQuery.add(QueryBuilders.rangeQuery("age").gte(21).lte(25));
        //多个条件的组合放到list集合中
        boolQueryBuilder.must().addAll(listQuery);
        boolQueryBuilder.mustNot().addAll(listQuery);
        boolQueryBuilder.should().addAll(listQuery);
        Query query = new NativeSearchQuery(boolQueryBuilder);
        SearchHits<Student> search = esTemplate.search(query, Student.class);
        List<Student> list = QueryESUtil.QueryES(search);
        //遍历students中的数据
        for (Student student : list) {
            System.out.println(student);
        }
    }
}

7.8 分页与排序

@SpringBootTest
class SpringdateEsApplicationTests {
    /**
     * ElasticsearchTemplate:底层基于transport  已经过时
     * ElasticsearchRestTemplate:底层基于rest-high-level
     */
    @Autowired
    private ElasticsearchRestTemplate esTemplate;
    @Test
    void docSearchDemo6(){
        //分页和排序
        Query query=new NativeSearchQuery(QueryBuilders.matchAllQuery());
        //设置分页的条件  注意 第一个参数代表的是页数(从0开始 0代表的是第一页)
        // 第二个参数代表的是每页条数
        // 我们进行分页查询的时候 给前台响应的数据 也要包含总条数 search.getTotalHits();
        query.setPageable(PageRequest.of(0,2));
        //排序的实现
        query.addSort(Sort.by(Sort.Direction.DESC,"age"));
        SearchHits<Student> search = esTemplate.search(query, Student.class);
        //search.getTotalHits();
        List<Student> list = QueryESUtil.QueryES(search);
        for (Student student : list) {
            System.out.println(student);
        }
    }
}

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

@Id
@Field(type = FieldType.Keyword)
private String id;

7.9 高亮查询

@SpringBootTest
class SpringdateEsApplicationTests {
    /**
     * ElasticsearchTemplate:底层基于transport  已经过时
     * ElasticsearchRestTemplate:底层基于rest-high-level
     */
    @Autowired
    private ElasticsearchRestTemplate esTemplate;
    @Test
    void docSearchDemo7(){
        //高亮查询
        Query query=new NativeSearchQuery(QueryBuilders.matchQuery("name","泰旭"));
        //设置高亮的条件
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        //高亮的字段
        highlightBuilder.field("name");
        //高亮字段的前缀
        highlightBuilder.preTags("<span style='color:red'>");
        //高亮字段的后缀
        highlightBuilder.postTags("</span>");
        //设置高亮查询
        HighlightQuery highlightQuery = new HighlightQuery(highlightBuilder);
        //把高亮查询设置到query对象中
        query.setHighlightQuery(highlightQuery);

        SearchHits<Student> search = esTemplate.search(query, Student.class);
        List<Student> students=new ArrayList<>();
        for (SearchHit<Student> searchHit : search) {
            Student student = searchHit.getContent();
            List<String> name = searchHit.getHighlightField("name");
            //把之前的name进行高亮替换
            student.setName(name.get(0));
            students.add(student);
        }
        for (Student student : students) {
            System.out.println(student);
        }
    }
}