ElasticSearch全文搜索引擎(二)-Spring Boot操作ES(SpringData概述、Spring Data Elasticsearch、基本操作、ElasticSearch操作文档)

1 Spring Data概述

  Spring Data是spring提供的一套连接各种第三方数据源的框架集,它支持连接很多第三方数据源,例如:

  • 数据库

  • redis

  • ElasticSearch

  • MongoDB等

  包括数据库在内,很多第三方数据都可以使用SpringData操作,非常方便。

 2 Spring Data Elasticsearch

  上面章节介绍了Spring Data可以连接很多第三方数据源,其中ES就是Spring Data可以连接的对象。原生情况下,我们需要使用socket来连接ES获得响应,再解析响应,代码量非常大,我们现在可以使用Spring Data提供的封装,连接ES,方便快捷。

转到knows-search模块:

  下面我们添加Spring Data ES的依赖:

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

  application.properties

 # 搜索微服务端口
 server.port=8066
 
 # 搜索微服务名称
 spring.application.name=search-service
 
 # 定位ES的位置
 spring.elasticsearch.rest.uris=http://localhost:9200
 
 # 设置日志门槛,显示ES的操作信息
 logging.level.cn.tedu.knows.search=debug
 # 还需要进一步设置才能使输出日志更清晰
 logging.level.org.elasticsearch.client.RestClient=debug

  SpringBoot启动类无需配置!

3 实现基本操作

  操作ES需要类:首先定义一个对应ES数据的类型,创建一个vo包,包中定义Item(商品)类代码如下:

 package cn.tedu.knows.search.vo;
 
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
 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;
 
 @Data   // lombok
 @Accessors(chain = true)   // 链式赋值(连续set方法)
 @AllArgsConstructor        // 全参构造
 @NoArgsConstructor         // 无参构造
 //指定当前类对象对应哪个ES中的索引
 //如果索引不存在,会自动创建
 @Document(indexName = "items")
 public class Item {
     // 表示当前ES索引的id列
     @Id
     private Long id;
     // 需要分词的属性使用Text类型,并指定分词器
     @Field(type = FieldType.Text,analyzer = "ik_smart",
             searchAnalyzer = "ik_smart")
     private String title;   //商品名称
     // 不需要分词的属性使用Keyword类型,不用写分词器
     @Field(type = FieldType.Keyword)
     private String category;//分类
 
     @Field(type = FieldType.Keyword)
     private String brand;   //品牌
 
     @Field(type = FieldType.Double)
     private Double price;   //价格
 
     //不会使用图片地址查询,设置index = false表示当前属性不会创建索引,节省空间,因为这个属性不会被查询
     @Field(type = FieldType.Keyword,index = false)
     private String images;  //图片地址
     //   /upload/2021/08/19/abc.jpg
 
 }
 

这个类中所有属性均配置了对应ES的属性和类型,下面我们就可以使用这个类操作ES了。

创建一个包repository,创建一个接口ItemRepository:

 @Repository //将实现类的对象存到Spring容器中
 //ElasticsearchRepository实现基本的增删改查
 public interface ItemRepository extends
                         ElasticsearchRepository<Item,Long> {
 }

这个接口和Mybatis Plus中Mapper接口继承的BaseMapper类似,会自动提供基本的增删改查方法。下面进行测试,测试类代码如下:

 package cn.tedu.knows.search;
 
 import cn.tedu.knows.search.repository.ItemRepository;
 import cn.tedu.knows.search.vo.Item;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.context.SpringBootTest;
 
 import javax.annotation.Resource;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 
 @SpringBootTest
 class KnowsSearchApplicationTests {
     @Resource
     ItemRepository itemRepository;
     @Test
     void contextLoads() {
         //实例化对象,新增一个商品
         Item item = new Item()
                .setId(1L)
                .setTitle("罗技激光无线游戏鼠标")
                .setCategory("鼠标")
                .setBrand("罗技")
                .setImages("/1.jpg")
                .setPrice(285.0);
         //调用Spring Data提供的方法进行新增,save表示新增
         itemRepository.save(item);
         System.out.println("ok");
    }
 
     //按id查询
     @Test
     public void getId(){
         //Optional表示装着Item的盒子
         Optional<Item> optional = itemRepository.findById(1L);
         //通过get进行查询
         System.out.println(optional.get());
    }
 
     //批量新增
     @Test
     public void addAll(){
         List<Item> list = new ArrayList<>();
         list.add(new Item(2L,"罗技机械无线游戏键盘","键盘","罗技",360.0,"/2.jpg"));
         list.add(new Item(3L,"雷蛇激光有线游戏鼠标","鼠标","雷蛇",488.0,"/3.jpg"));
         list.add(new Item(4L,"罗技降噪蓝牙竞技耳机","耳机","罗技",378.0,"/4.jpg"));
         list.add(new Item(5L,"华为静音办公有线鼠标","鼠标","华为",220.0,"/5.jpg"));
         list.add(new Item(6L,"雷蛇竞技机械无线键盘","键盘","雷蛇",425.0,"/6.jpg"));
         itemRepository.saveAll(list);
         System.out.println("ok");
    }
 
     //全查
     @Test
     public void getAll(){
         //Iterable是List的父接口
         Iterable<Item> list = itemRepository.findAll();
         for(Item item : list){
             System.out.println(item);
        }
    }
 
 }
 //上面都是基本操作,不需要我们自己写接口中的方法

上面进行了单增、单查、批量增和全查的操作,下面进行自定义的查询。

Spring Data支持编写方法名表达操作,会自动按方法名的表达生成实现代码,这是它的一大优势!

在ItemRepository接口编写方法:

 // Spring Data框架连接数据源,可以通过方法名来表达操作含义
 // 根据商品的title属性执行模糊查询
 Iterable<Item> queryItemsByTitleMatches(String title);

测试代码:

 // 下面要完成一些条件查询,需要调用ItemRepository接口中编写的方法
 // 商品标题模糊匹配
 @Test
 public void queryByTitle(){
     Iterable<Item> items=itemRepository.queryItemsByTitleMatches("无线");
     for(Item item: items){
         System.out.println(item);
    }
 }

相当于运行了下面的指令:

 ### 单条件搜索
 POST http://localhost:9200/items/_search
 Content-Type: application/json
 
 {
  "query": {"match": { "title": "无线" }}
 }

多属性条件查询:在ItemRepository接口编写方法:

 // 根据商品的title和brand执行模糊查询
 Iterable<Item> queryItemsByTitleMatchesAndBrandMatches(String title,String brand);

测试类:

 // 测试多条件查询
 @Test
 public void queryByTitleBrand(){
    Iterable<Item> items=itemRepository
            .queryItemsByTitleMatchesAndBrandMatches(
                    "游戏","罗技"
            );
    for(Item item: items){
        System.out.println(item);
    }
 }

实际执行的请求:

 ### 多字段搜索
 POST http://localhost:9200/items/_search
 Content-Type: application/json
 
 {
  "query": {
    "bool": {
      "must": [
        { "match": { "title": "游戏" }},
        { "match": { "brand": "罗技"}}
      ]
    }
  }
 }

排序查询:在ItemRepository接口编写方法:

 // 排序查询:按照价格降序查询标题或者品牌匹配的商品
 Iterable<Item> queryItemsByTitleMatchesOrBrandMatchesOrderByPriceDesc(String title,String brand);

测试代码:

 // 测试排序
 @Test
 public void order(){
     Iterable<Item> items = itemRepository.queryItemsByTitleMatchesOrBrandMatchesOrderByPriceDesc("游戏","罗技");
     for(Item item:items){
         System.out.println(item);
    }
 }

实际运行的请求:

 ### 多字段搜索
 
 POST http://localhost:9200/items/_search
 Content-Type: application/json
 
 {
  "query": {
    "bool": {
      "should": [
        { "match": { "title": "游戏" }},
        { "match": { "brand": "罗技"}}
      ]
    }
  },"sort":[{"price":"desc"}]
 }

添加分页查询功能::在ItemRepository接口编写方法

 // 分页查询 Page相当于PageHelper,Pageable规定第几页包含多少行,相当于PageInfo
 Page<Item> queryItemsByTitleMatchesOrBrandMatchesOrderByPriceDesc(String title, String brand, Pageable pageable);

测试:

 //分页查询
 @Test
 public void page(){
     int pageNum=1;
     int pageSize=2;
     //PageRequest.of返回Pageable,Pageable页数从第0页开始
     Page<Item> page=itemRepository.queryItemsByTitleMatchesOrBrandMatchesOrderByPriceDesc(
                     "游戏","罗技",PageRequest.of(pageNum-1,pageSize));
     //page实现了Iterable接口
     for(Item item:page){
         System.out.println(item);
    }
 }

4.ElasticSearch操作文档

 ### 创建 index
 PUT http://localhost:9200/questions
 ### 删除一个Index
 DELETE http://localhost:9200/questions
 ### 设置index中的文档属性采用ik分词
 ### type=text的才能分词,analyzer表示分词器,根据分词器对text内容进行分词,建立索引
 ### search_analyzer表示搜索内容的分词器,一般与上面的分词器相同,建立索引
 ### _mapping配合properties用来设置属性
 ### 注意下面的换行,这里是回车并换行,有严格格式要求,必须这样书写
 POST http://localhost:9200/questions/_mapping
 Content-Type: application/json
 
 {
  "properties": {
    "title": {
      "type": "text",
      "analyzer": "ik_max_word",
      "search_analyzer": "ik_max_word"
    },
    "content": {
      "type": "text",
      "analyzer": "ik_max_word",
      "search_analyzer": "ik_max_word"
    }
  }
 }
 ### questions中添加文档
 ### POST一般为新增或修改的意思,_create表示创建文档,/1中的1表示文档id,为真正的id
 ### 每执行一次请求必须通过###来分割,既是分隔符,也是注释符
 POST http://localhost:9200/questions/_create/1
 Content-Type: application/json
 
 {
  "id":1,
  "title":"Java基本数据类型有哪些",
  "content":"面时候为啥要问基本类型这么简单问题呀,我们要如何回答呢?"
 }
 
 ### questions 中添加文档
 POST http://localhost:9200/questions/_create/2
 Content-Type: application/json
 
 {
  "id":2,
  "title":"int类型的范围",
  "content":"为啥要了解int类型的范围呢?"
 }
 
 ### questions 中添加文档
 POST http://localhost:9200/questions/_create/3
 Content-Type: application/json
 
 {
  "id":3,
  "title":"常用集合类有哪些",
  "content":"为啥企业经常问集合呀?该如何回复呢"
 }
 
 ### questions 中添加文档
 POST http://localhost:9200/questions/_create/4
 Content-Type: application/json
 
 {
  "id":4,
  "title":"线程的run方法和start方法有啥区别",
  "content":"run方法可以执行线程的计算过程, start也可以执行线程的计算过程,用途一样么?"
 }
 ### 更新questions索引中的文档
 ### 此处POST是更新的意思,表示对文档4进行更新
 POST http://localhost:9200/questions/_doc/4/_update
 Content-Type: application/json
 
 {
  "doc": {
    "title": "Java线程的run方法和start方法有啥区别"
  }
 }
 ### 删除questions中的一个文档,DELETE表示删除
 DELETE http://localhost:9200/questions/_doc/2
 ### 查询数据,GET表示查询
 GET http://localhost:9200/questions/_doc/4
 ### 分词搜索 单属性模糊查询 查询分词索引,按照输出得分(_score:查询内容占整个内容的比例)由高到低排序
 POST http://localhost:9200/questions/_search
 Content-Type: application/json
 
 {
  "query": { "match": {"title": "类型" } }
 }
 ### 多字段搜索 多属性模糊查询 格式固定
 ### bool表示真假,should表示或,must表示与
 ### 查询的内容也会分词
 POST http://localhost:9200/questions/_search
 Content-Type: application/json
 
 {
  "query": {
    "bool": {
      "should": [
        { "match": { "title": "java类型" }},
        { "match": { "content": "java类型"}}
      ]
    }
  }
 }

 

posted @ 2021-09-17 17:46  Coder_Cui  阅读(532)  评论(0编辑  收藏  举报