一、主要内容

  • spring boot 引入Elasticsearch
  • ElasticsearchTemplate的使用
  • ElasticsearchRepository的使用

    二、环境整合

    创建Elasticsearch工程,引入依赖

    一般情况下,都会单独创建一个工程,用于操作es。

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

    版本统一

    目前springboot-data-elasticsearch最新版本是2.5.1,这个版本使用的es版本为7.12.1,需要注意的是,我们这里引入的版本需要与服务器安装的版本一致。
    可以通过Maven仓库进行查看版本信息,地址:mvnrepository.com/artifact/or…

    修改配置文件

    spring:
    data:
    elasticsearch:
    cluster-name: es-cluster
    cluster-nodes: 172.18.28.55:9300
    复制代码

    三、ElasticsearchTemplate的使用

    ElasticsearchTemplate类似于RedisTemplate,是spring提供的较为底层的与Elasticserch交互的类。

    常用注解

    @Document

    注解作用在类上,标记实体类为文档对象,指定实体类与索引对应关系。常用配置项有:

  • indexName:索引名称

  • type: 索引类型,默认为空
  • shards: 主分片数量,默认5
  • replicas:复制分片数量,默认1
  • createIndex:创建索引,默认为true

    @Id

    指定文档ID,加上这个注解,文档的_id会与我们的数据ID是一致的,否则在不给定默认值的情况下,es会自动创建。

    @Field

    指定普通属性,标明这个是文档中的一个字段。常用的配置项有:

  • type: 对应Elasticsearch中属性类型。默认自动检测。使用FiledType枚举可以快速获取

public enum FieldType {
Text,//会进行分词并建立索引的字符类型
Integer,
Long,
Date,
Float,
Double,
Boolean,
Object,
Auto,//自动判断字段类型
Nested,//嵌套对象类型
Ip,
Attachment,
Keyword//不会进行分词建立索引的类型
}
复制代码

  • index: 是否创建倒排索引,一般不需要分词的属性不需要创建索引
  • analyzer:指定索引类型。
  • store:是否进行存储,默认不进行存储。其实不管我们将store值设置为true或false,elasticsearch都会将该字段存储到Field域中;但是他们的区别是什么?那么我们在什么样的业务场景下使用store field功能?

    1. store = false时,默认设置;那么给字段只存储在”_source”的Field域中;
    2. store = true时,该字段的value会存储在一个跟_source平级的独立Field域中;同时也会存储在_source中,所以有两份拷贝。
    3. _source field在索引的mapping 中disable了。这种情况下,如果不将某个field定义成store=true,那些将无法在返回的查询结果中看到这个field。
    4. _source的内容非常大。这时候如果我们想要在返回的_source document中解释出某个field的值的话,开销会很大(当然你也可以定义source filtering将减少network overhead),比例某个document中保存的是一本书,所以document中可能有这些field: title, date, content。假如我们只是想查询书的title 跟date信息,而不需要解释整个_source(非常大),这个时候我们可以考虑将title, date这些field设置成store=true。

      创建实体

      // 关联的索引是item,类型是_doc,直接使用而不创建索引
      @Document(indexName = “item”,type = “_doc”,createIndex = “false”)
      public class Item {
      @Id
      private Long id;
      // title使用ik进行分词
      @Field(type = FieldType.Text,analyzer = “ik_max_word”)
      private String title;
      // brand 不被分词
      @Field(type=FieldType.Keyword)
      private String brand;
      @Field(type=FieldType.Double)
      private Double price;
      // brand 不被分词,且不创建索引
      @Field(index = false,type = FieldType.Keyword)
      private String images;
      }
      复制代码

      索引管理

      ElasticsearchTemplate提供了创建索引的方法,但是不建议使用 ElasticsearchTemplate 对索引进行管理(创建索引,更新映射,删除索引)。
      索引就像是数据库或者数据库中的表,我们平时是不会是通过java代码频繁的去创建修改删除数据库或者表的相关信息,我们只会针对数据做CRUD的操作。
      // 创建索引
      elasticsearchTemplate.createIndex(Class clazz);
      // 删除索引,有好几个方法,有兴趣的同学可以自行翻阅源码
      elasticsearchTemplate.deleteIndex(Class clazz);
      复制代码

      创建文档

      新增单条文档

      @RunWith(SpringRunner.class)
      @SpringBootTest(classes = Application.class)
      public class ESTest {
      @Autowired
      private ElasticsearchTemplate elasticsearchTemplate;
      @Test
      public void insertItemDoc(){
      Item item = new Item(1001L,”XXX1”,”XXX1”,”XXX1”);
      IndexQuery indexQuery = new IndexQueryBuilder().withObject(item).build();
      elasticsearchTemplate.index(indexQuery);
      }
      }
      复制代码

      批量新增文档

      @RunWith(SpringRunner.class)
      @SpringBootTest(classes = Application.class)
      public class ESTest {
      @Autowired
      private ElasticsearchTemplate elasticsearchTemplate;
      @Test
      public void insertItemDocBulk() {
      List list = new ArrayList<>();
      list.add(new IndexQueryBuilder().withObject(new Item(1001L,”XXX1”,”XXX1”,”XXX1”)).build())
      list.add(new IndexQueryBuilder().withObject(new Item(1002L,”XXX2”,”XXX2”,”XXX2”)).build())
      IndexQuery indexQuery = new IndexQueryBuilder().withObject(item).build();
      elasticsearchTemplate.index(indexQuery);
      }
      }
      复制代码

      删除文档

      删除都是根据主键进行删除,提供了两个方法:
  • delete(String indexName,String typeName,String id); 通过字符串指定索引,类型和id值;

  • delete(Class,String id) 第一个参数传递实体类类类型,建议使用此方法,减少索引名和类型名由于手动编写出现错误的概率。

代码比较简单,不再示例。

修改文档

@Test
public void updateItemDoc() {
Map sourceMap = new HashMap<>();
sourceMap.put(“title”, “YYY”);

  1. IndexRequest indexRequest = new IndexRequest();<br /> indexRequest.source(sourceMap);
  2. UpdateQuery updateQuery = new UpdateQueryBuilder()<br /> .withClass(Stu.class)<br /> .withId("1001")<br /> .withIndexRequest(indexRequest)<br /> .build();<br /> elasticsearchTemplate.update(updateQuery);<br />}<br />复制代码

查询文档

模糊查询

使用查询条件与所有的field进行匹配,进行查询
对于一些入参是不是看着很陌生?感觉头大是不是,别急,最后有解释。
@Test
public void queryItemDoc() {
// 少年去和所有field进行匹配
QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery(“少年”);
// 查询条件SearchQuery是接口,只能实例化实现类。
SearchQuery searchQuery = new NativeSearchQuery(queryStringQueryBuilder);
List list = elasticsearchTemplate.queryForList(searchQuery, Item.class);
for(Item item : list){
System.out.println(item);
}
}
复制代码

使用match_all查询所有文档

@Test
public void matchAllItemDoc() {
SearchQuery searchQuery = new NativeSearchQuery(QueryBuilders.matchAllQuery());
List list = elasticsearchTemplate.queryForList(searchQuery, Item.class);
for(Item item : list){
System.out.println(item);
}
}
复制代码

使用match查询分页文档并排序

@Test
public void matchItemDoc() {
// 构造分页信息:第一个参数是页码,从0算起。第二个参数是每页显示的条数
Pageable pageable = PageRequest.of(0, 2);
// 构造排序信息
SortBuilder sortBuilder = new FieldSortBuilder(“price”)
.order(SortOrder.DESC);
SearchQuery query = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchQuery(“title”, “english”))
.withPageable(pageable)
.withSort(sortBuilder)
.build();
AggregatedPage pagedItem = elasticsearchTemplate.queryForPage(query, Item.class);
System.out.println(“查询后的总分页数目为:” + pagedItem.getTotalPages());
List list = pagedItem.getContent();
for (Item item : list) {
System.out.println(item);
}
}
复制代码

多条件查询

@Test
public void mustShouldItemDoc(){
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
List listQuery = new ArrayList<>();
listQuery.add(QueryBuilders.matchPhraseQuery(“title”,”XX”));
listQuery.add(QueryBuilders.rangeQuery(“price”).gte(10).lte(100));
//boolQueryBuilder.should().addAll(listQuery); // 逻辑或
boolQueryBuilder.must().addAll(listQuery); // 逻辑与
SearchQuery searchQuery = new NativeSearchQuery(boolQueryBuilder);
List list = elasticsearchTemplate.queryForList(searchQuery, Item.class);
for(Item item : list){
System.out.println(item);
}
}
复制代码

高亮查询

@Test
public void highlightStuDoc(){
// 指定高亮的信息用什么标签包裹,默认使用
String preTag = ““;
String postTag = “
“;

  1. Pageable pageable = PageRequest.of(0, 10);
  2. SortBuilder sortBuilder = new FieldSortBuilder("price")<br /> .order(SortOrder.DESC);<br /> // 构造查询规则<br /> SearchQuery query = new NativeSearchQueryBuilder()<br /> .withQuery(QueryBuilders.matchQuery("title", "XXX"))<br /> .withHighlightFields(new HighlightBuilder.Field("title")<br /> .preTags(preTag)<br /> .postTags(postTag))<br /> .withSort(sortBuilder)<br /> .withPageable(pageable)<br /> .build();<br /> AggregatedPage<Item> paged = esTemplate.queryForPage(query, Item.class, new SearchResultMapper() {<br /> @Override<br /> public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {<br /> // 定义一个list存放最终返回的数据<br /> List<Item> listHighlight = new ArrayList<>();<br /> // 取出命中的信息<br /> SearchHits hits = response.getHits();<br /> for (SearchHit h : hits) {<br /> // 取出高亮的数据<br /> HighlightField highlightField = h.getHighlightFields().get("title");<br /> String title = highlightField.getFragments()[0].toString();<br /> // 取出其他数据<br /> Object id = (Object)h.getSourceAsMap().get("id");<br /> String brand = (String)h.getSourceAsMap().get("brand");<br /> Object price = (Object)h.getSourceAsMap().get("price");<br /> String images = (String)h.getSourceAsMap().get("images");<br /> // 处理数据<br /> Item itemHL = new Item();<br /> item.setId(Long.valueOf(id.toString()));<br /> item.setTitle(title);<br /> item.setBrand(brand);<br /> item.setPrice(Double.valueof(price.toString));<br /> item.setImages(images);
  3. listHighlight.add(itemHL);<br /> }
  4. if (listHighlight.size() > 0) {<br /> return new AggregatedPageImpl<>((List<T>)listHighlight);<br /> }<br /> return null;<br /> }<br /> });<br /> System.out.println("查询后的总分页数目为:" + paged.getTotalPages());<br /> List<Item> list = paged.getContent();<br /> for (Item item : list) {<br /> System.out.println(item);<br /> }<br />}<br />复制代码

四、ElasticsearchRepository的使用

ElasticsearchRepository是基于ElasticsearchTemplate封装的一个接口用于操作Elasticsearch。
它主要干了啥呢?我们能通过它干些什么呢?

  • 它主要帮我们封装了一些常用的方法。如下图:Spring Boot整合Elasticsearch详细教程 - 图1
  • 我们还可以通过使用NotAddLikeOrBetween等关键词自动创建查询语句。它会自动帮你完成,无需写实现类。比如:你的方法名叫做:findByTitle,那么它就知道你是根据title查询,然后自动帮你完成,无需写实现类。【自定义方法命名约定】:Spring Boot整合Elasticsearch详细教程 - 图2

    自定义Repository接口

    这个是遵循SpringData的规范,在自定义接口中直接指定查询方法名称便可查询,无需进行实现。在idea里面也会提示ES里面有的字段,写起来挺方便的。
    /
    需要继承ElasticsearchRepository接口
    /
    public interface ItemRepository extends ElasticsearchRepository {
    /

    方法名必须遵守SpringData的规范
    价格区间查询
    */
    List findByPriceBetween(double price1, double price2);
    }
    复制代码

    创建文档

    新增单条文档

    可以直接调用ItemRepository的save()方法。
    @Autowired
    private ItemRepository itemRepository;
    @Test
    public void insert(){
    Item item = new Item(1L, “小米手机7”, “小米”, 3499.00, “http://xxx/xxx.jpg“);
    itemRepository.save(item);
    }
    复制代码

    批量新增文档

    插入多条文档数据使用的saveAll()方法
    @Autowired
    private ItemRepository itemRepository;
    @Test
    public void indexList() {
    List list = new ArrayList<>();
    list.add(new Item(1L, “小米手机7”, “小米”, 3299.00, “http://xxx/1.jpg“));
    list.add(new Item(2L, “坚果手机R1”, “锤子”, 3699.00, “http://xxx/1.jpg“));
    list.add(new Item(3L, “华为META10”, “华为”, 4499.00, “http://xxx/1.jpg“));
    list.add(new Item(4L, “小米Mix2S”, “小米”, 4299.00, “http://xxx/1.jpg“));
    list.add(new Item(5L, “荣耀V10”, “华为”, 2799.00, “http://xxx/1.jpg“));
    // 接收对象集合,实现批量新增
    itemRepository.saveAll(list);
    }
    复制代码

    修改文档

    修改文档数据也是使用的save()方法。

    查询参数介绍

    我们通过上面的方法截图,可以看出来ElasticsearchRepository提供了一些特殊的search()方法,用来构建一些查询。这几个方法里面的参数主要是SearchQuery和QueryBuilder,所以要完成一些特殊查询主要就是构建这两个参数。
    SearchQuery
    通过源码我们可以看到,SearchQuery是一个接口,有一个实现类叫NativeSearchQuery,实际使用中,我们的主要任务就是构建NativeSearchQuery来完成一些复杂的查询的。
    Spring Boot整合Elasticsearch详细教程 - 图3
    我们可以看到NativeSearchQuery的主要入参是QueryBuilder、SortBuilder、HighlightBuilder
    一般情况下,我们不是直接是new NativeSearchQuery,而是使用NativeSearchQueryBuilder来完成NativeSearchQuery的构建。
    NativeSearchQueryBuilder
    .withQuery(QueryBuilder1)
    .withFilter(QueryBuilder2)
    .withSort(SortBuilder1)
    .withXXXX()
    .build();
    复制代码
    QueryBuilder
    QueryBuilder是一个接口,它有非常多的实现类,可以用于不同的查询条件构建。
    要构建QueryBuilder,我们可以使用工具类QueryBuilders,里面有大量的方法用来完成各种各样的QueryBuilder的构建,字符串的、Boolean型的、match的、地理范围的等等。
    // 构建一个普通的查询
    SearchQuery searchQuery = new NativeSearchQueryBuilder()
    .withQuery(QueryBuilders.queryStringQuery(“spring boot OR 书籍”))
    .build();
    复制代码
    查询方法跟上面介绍的ElasticsearchTemplate差不多,就不再单独举例了。
    综上,我们在一般的场景下使用ElasticsearchRepository就可以满足我们的需要,在一些复杂的查询场景下,可以配合ElasticsearchTemplate来使用。