Java操作ElasticSearch有两种方式:

a、ElasticSearch提供的客户端API

  1. <dependency>
  2. <groupId>org.elasticsearch.client</groupId>
  3. <artifactId>transport</artifactId>
  4. <version>7.6.2</version>
  5. </dependency>

b、Spring Data ElasticSearch

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

在这里我们使用SpringData

1、SpringData ElasticSearch 项目环境搭建

1-1 创建项目

image.png
image.png

1-2 修改pom.xml

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

在项目pom.xml中添加依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
  4. </dependency>

image.png

1-3 修改配置文件

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

  1. spring:
  2. elasticsearch:
  3. rest:
  4. uris: http://192.168.163.12:9200

1-4 新建索引和指定mapping

image.png

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

2、 ElasticsearchTemplate的使用

2-1 初始化索引

  1. @SpringBootTest
  2. class EsDemoApplicationTests {
  3. @Autowired
  4. private ElasticsearchRestTemplate elasticsearchRestTemplate;
  5. @Test
  6. void contextLoads() {
  7. //调用代码,实现映射关系
  8. // SpringData 4.x操作索引都是通过IndexOptions进行操作
  9. IndexOperations operations = elasticsearchRestTemplate.indexOps(Item.class);
  10. //创建索引
  11. operations.create();
  12. //createMapping 根据实体类获取映射关系
  13. //putMapping 把映射关系添加到索引中
  14. Document mapping = operations.createMapping();
  15. boolean b = operations.putMapping(mapping);
  16. System.out.println("索引创建:" + b);
  17. }
  18. }

2-2 删除索引

  1. /**
  2. * 删除索引
  3. */
  4. @Test
  5. void deleteIndex(){
  6. IndexOperations operations = elasticsearchRestTemplate.indexOps(Item.class);
  7. operations.delete();
  8. }

2-3 添加文档

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

2-3-1 新增单个文档

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

  1. /**
  2. * 新增单个文档
  3. */
  4. @Test
  5. void addDocument(){
  6. Item item = new Item("id", "标题", 1110000l, "catName");
  7. Item save = elasticsearchRestTemplate.save(item);
  8. System.out.println("新增单个文档:"+save);
  9. }
  10. /**
  11. * 新增单个文档(不指定ID)
  12. */
  13. @Test
  14. void addDocument(){
  15. Item item = new Item(null, "标题", 1110000l, "catName");
  16. Item save = elasticsearchRestTemplate.save(item);
  17. System.out.println("新增单个文档:"+save);
  18. }
  19. //输出:新增单个文档:Item{id='BiAkpIAB5cT0lDi1vRvj', title='标题', price=1110000, catName='catName'}

2-3-2 批量新增

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

2-4 删除文档

  1. /**
  2. * 删除文档
  3. */
  4. @Test
  5. void deleteDocument(){
  6. String id = elasticsearchRestTemplate.delete("id", Item.class);
  7. System.out.println("删除文档:"+id);
  8. }

2-5 修改文档

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

2-6 查询

2.6-1 根据主键查询

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

2-6-2 模糊查询

  1. /**
  2. * 模糊查询
  3. * 去所有field中查询指定条件。
  4. */
  5. @Test
  6. void search(){
  7. QueryStringQueryBuilder queryBuilder = QueryBuilders.queryStringQuery("标题");
  8. Query query = new NativeSearchQuery(queryBuilder);
  9. // 相当于最外层hits
  10. SearchHits<Item> searchHits = elasticsearchRestTemplate.search(query,Item.class);
  11. // 里层的hits
  12. List<SearchHit<Item>> list = searchHits.getSearchHits();
  13. // 进行转换
  14. List<Item> listResult = new ArrayList<>();
  15. list.forEach(sh -> {
  16. listResult.add(sh.getContent());
  17. });
  18. System.out.println(listResult);
  19. }

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

  1. /**
  2. * 使用match_all 查询所有文档
  3. */
  4. @Test
  5. void matchAllSearch(){
  6. Query query = new NativeSearchQuery(QueryBuilders.matchAllQuery());
  7. SearchHits<Item> searchHits = elasticsearchRestTemplate.search(query, Item.class);
  8. List<Item> listRes = new ArrayList<>();
  9. searchHits.forEach(sh->{
  10. listRes.add(sh.getContent());
  11. });
  12. System.out.println("res:"+listRes);
  13. }

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

  1. /**
  2. * 使用match 查询文档
  3. */
  4. @Test
  5. void matchSearch(){
  6. Query query = new NativeSearchQuery(QueryBuilders.matchQuery("title","华为"));
  7. SearchHits<Item> searchHits = elasticsearchRestTemplate.search(query, Item.class);
  8. List<SearchHit<Item>> searchHitList = searchHits.getSearchHits();
  9. List<Item> res = new ArrayList<>();
  10. searchHitList.forEach(sh->{
  11. res.add(sh.getContent());
  12. });
  13. System.out.println("res:"+res);
  14. }

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

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

  1. /**
  2. * 短语搜索是对条件不分词,但是文档中属性根据配置实体类时指定的分词类型进行分词。
  3. * 如果属性使用ik分词器,从分词后的索引数据进行匹配。
  4. */
  5. @Test
  6. void matchPhrase(){
  7. Query searchQuery = new NativeSearchQuery(QueryBuilders.matchPhraseQuery("title","手机"));
  8. SearchHits<Item> searchHits = elasticsearchRestTemplate.search(searchQuery, Item.class);
  9. List<SearchHit<Item>> searchHitList = searchHits.getSearchHits();
  10. List<Item> res = new ArrayList<>();
  11. searchHitList.forEach(sh->{
  12. res.add(sh.getContent());
  13. });
  14. System.out.println("res:"+res);
  15. }

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

  1. @Test
  2. void range(){
  3. Query searchQuery = new NativeSearchQuery(QueryBuilders.rangeQuery("price").gte(100).lte(5000));
  4. SearchHits<Item> searchHits = elasticsearchRestTemplate.search(searchQuery, Item.class);
  5. List<SearchHit<Item>> searchHitList = searchHits.getSearchHits();
  6. List<Item> res = new ArrayList<>();
  7. searchHitList.forEach(sh->{
  8. res.add(sh.getContent());
  9. });
  10. System.out.println("res:"+res);
  11. }

2-6-7 多条件查询

  1. @Test
  2. void mustShould(){
  3. BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
  4. List<QueryBuilder> listQuery = new ArrayList<>();
  5. listQuery.add(QueryBuilders.matchPhraseQuery("title","手机"));
  6. listQuery.add(QueryBuilders.rangeQuery("price").gte(1000).lte(6000));
  7. // boolQueryBuilder.should().addAll(listQuery);// ||
  8. boolQueryBuilder.must().addAll(listQuery);// &&
  9. Query query = new NativeSearchQuery(boolQueryBuilder);
  10. SearchHits<Item> search = elasticsearchRestTemplate.search(query, Item.class);
  11. List<Item> res = new ArrayList<>();
  12. for (SearchHit<Item> searchHit : search.getSearchHits()) {
  13. res.add(searchHit.getContent());
  14. }
  15. System.out.println("res:"+res);
  16. }

2-6-8 分页与排序

  1. /**
  2. * 分页与排序
  3. */
  4. @Test
  5. void PageSort(){
  6. Query query = new NativeSearchQuery(QueryBuilders.matchAllQuery());
  7. query.setPageable(PageRequest.of(0,2));
  8. query.addSort(Sort.by(Sort.Direction.DESC,"price"));
  9. SearchHits<Item> search = elasticsearchRestTemplate.search(query, Item.class);
  10. for (SearchHit<Item> searchHit : search.getSearchHits()) {
  11. System.out.println("res:"+searchHit.getContent());
  12. }
  13. }
  1. 如果实体类中主键只有@Id注解,String id对应ES中是text类型,text类型是不允许被排序,所以如果必须按照主键进行排序时需要在实体类中设置主键类型<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/872322/1651996044708-cd75bc8f-31f4-4ae0-add1-542e8cab1800.png#clientId=ua4eb7924-e161-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=289&id=uae7eee20&margin=%5Bobject%20Object%5D&name=image.png&originHeight=289&originWidth=750&originalType=binary&ratio=1&rotation=0&showTitle=false&size=30925&status=done&style=none&taskId=u9cbaa22d-f774-4f4d-aa55-a8336d4d969&title=&width=750)

2-6-9 高亮显示

  1. @Test
  2. void highLight(){
  3. NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(QueryBuilders.matchQuery("catName", "手机"));
  4. // 排序
  5. nativeSearchQuery.addSort(Sort.by(Sort.Direction.DESC,"price"));
  6. // 分页
  7. nativeSearchQuery.setPageable(PageRequest.of(0,2));
  8. // 设置高亮条件
  9. HighlightBuilder hlBuilder = new HighlightBuilder();
  10. // 哪个属性高亮
  11. hlBuilder.field("catName");
  12. // 高亮内容前缀
  13. hlBuilder.preTags("<span style='color:red'>");
  14. // 高亮内容后缀
  15. hlBuilder.postTags("</span>");
  16. // 高亮查询
  17. HighlightQuery hlQuery = new HighlightQuery(hlBuilder);
  18. // 应用高亮
  19. nativeSearchQuery.setHighlightQuery(hlQuery);
  20. // 外层hits
  21. SearchHits<Item> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Item.class);
  22. // 查询出的总条数
  23. System.out.println(searchHits.getTotalHits());
  24. // 里层hits
  25. List<SearchHit<Item>> searchHitList = searchHits.getSearchHits();
  26. List<Item> list = new ArrayList<>();
  27. searchHitList.forEach(sh ->{
  28. // 取出非高亮数据
  29. Item peo = sh.getContent();
  30. // 获取高亮数据
  31. String hlContent = sh.getHighlightField("catName").get(0);
  32. // 用高亮数据替换非高亮数据
  33. peo.setCatName(hlContent);
  34. list.add(peo);
  35. });
  36. System.out.println(list);
  37. }