五阶段--Spring Data Elasticsearch - 增删改查API / 使用 Criteria 构建查询 / Spring Data ES 分页操作 /拼多商城搜索条

Spring Data Elasticsearch


https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#reference

Spring Data Elasticsearch 是 Elasticsearch 搜索引擎开发的解决方案。它提供:

模板对象,用于存储、搜索、排序文档和构建聚合的高级API。

案例说明


在 Elasticsearch 中存储学生数据,并对学生数据进行搜索测试。


数据结构:

学号    姓名    性别    出生日期
 27       张三    男       2020-12-4

案例测试以下数据操作:

学号姓名性别出生日期
27张三2020-12-4

创建 students 索引和映射
C - 创建学生数据
R - 访问学生数据
U - 修改学生数据
D - 删除学生数据
使用 Repository 和 Criteria 搜索学生数据


 

一  新建spring工程,添加elasticsearch依赖,修改pom版本,以及编辑配置文件

spring:
  elasticsearch:
    rest:
      uris:
        - http://192.168.64.181:9200
        - http://192.168.64.181:9201
        - http://192.168.64.181:9202

logging:
  level:
    tracer: TRACE

二  在 Elasticsearch 中创建 students 索引

在开始运行测试之前,在 Elasticsearch 中先创建 students 索引:

PUT /students
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 2,
    "index.max_ngram_diff":30,
    "analysis": {
      "analyzer": {
        "ngram_analyzer": {
          "tokenizer": "ngram_tokenizer"
        }
      },
      "tokenizer": {
        "ngram_tokenizer": {
          "type": "ngram",
          "min_gram": 1,
          "max_gram": 30,
          "token_chars": [
            "letter",
            "digit"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "id": {
        "type": "long"
      },
      "name": {
        "type": "text",
        "analyzer": "ngram_analyzer"
      },
      "gender": {
        "type": "keyword"
      },
      "birthDate": {
        "type": "date",
        "format": "yyyy-MM-dd"
      }
    }
  }
}

 

 三 添加Lombok依赖,封装学生数据对象

  • 封装的数据对象
/**
 * @Document 用于指定索引,也可以用来自动创建索引,
 * 一般索引是自己手动创建*/
@Document(indexName = "students",shards = 3,replicas = 2)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    @Id //使用学生学号作为索引id()
    private Long id;
    private String name;
    private Character gender;
    //设置索引中的字段名,对应实体类中的属性
    //若字段名和属性名相同,可以省略此注解
    @Field("birthDate")
    private String birthDate;
}
  • 新建接口
package cn.tedu.es;

import cn.tedu.es.entity.Student;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
/**Spring Data Repository 规范
* 只需要定义抽象接口,抽象方法,不需要自己实现
* Repository 底层代码已经实现了所有的增删改成的数据操作代码
* */
public interface StudentRepository
        extends ElasticsearchRepository<Student,Long> {//数据类型Student,索引类型Long

}

四 通过 ElasticsearchRepository 实现 CRUD 操作

Spring Data 的 Repository 接口提供了一种声明式的数据操作规范,无序编写任何代码,只需遵循 Spring Data 的方法定义规范即可完成数据的 CRUD 操作。

ElasticsearchRepository 继承自 Repository,其中已经预定义了基本的 CURD 方法,我们可以通过继承 ElasticsearchRepository,添加自定义的数据操作方法。

Repository 方法命名规范

自定义数据操作方法需要遵循 Repository 规范,示例如下:

Elasticsearch(五)Spring Data Elasticsearch - 增删改查API_wanght笔记-CSDN博客

五   创建测试类,添加数据并保存,增删改查

package cn.tedu.es;

import cn.tedu.es.entity.Student;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Optional;

@SpringBootTest
public class Test1 {
    @Autowired
    private StudentRepository r;

    @Test  //import org.junit.jupiter.api.Test;
    public void test1() {
        Student s1 = new Student(9527L, "唐伯虎", '男', "2021-09-29");
        Student s2 = new Student(9528L, "华夫人", '女', "2021-08-29");
        Student s3 = new Student(9529L, "祝枝山", '男', "2021-07-29");
        Student s4 = new Student(9530L, "华安", '男', "2021-06-29");
        Student s5 = new Student(9531L, "旺财", '男', "2021-05-29");
        Student s6 = new Student(9532L, "小强", '男', "2021-04-29");
        Student s7 = new Student(9533L, "秋香", '女', "2021-03-29");
        Student s8 = new Student(9534L, "石榴姐", '男', "2021-02-29");
        r.save(s1);
        r.save(s2);
        r.save(s3);
        r.save(s4);
        r.save(s5);
        r.save(s6);
        r.save(s7);
        r.save(s8);
    }
    //修改
    @Test
    public void test2(){
        Student student = new Student(9533L, "零零", '女', "2021-03-09");
        r.save(student);
    }
    //查询
    @Test
    public void test3(){
        Optional<Student> o1 = r.findById(9533L);
        if (o1.isPresent()) { //是否存在/出现
            Student student = o1.get();
            System.out.println(student);
        }
        System.out.println("--------------------");
        Iterable<Student> all = r.findAll();
        for (Student s: all){
            System.out.println(s);
        }
    }
    //删除
    @Test
    public void test4(){
        r.deleteById(9533L);
    }
}

六 搜索操作

  • 接口添加搜索的抽象方法
package cn.tedu.es;

import cn.tedu.es.entity.Student;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import java.util.List;

/**Spring Data Repository 规范
* 只需要定义抽象接口,抽象方法,不需要自己实现
* Repository 底层代码已经实现了所有的增删改成的数据操作代码
* */
public interface StudentRepository
        extends ElasticsearchRepository<Student,Long> {//数据类型Student,索引类型Long
    // 在 name 中搜索关键词
    List<Student> findByName(String nameKey);

    // 在 name 和 birthDate 中搜索
    List<Student> findByNameOrBirthDate(String name,String birthDate);

}
  • 测试类编辑搜索方法
   //根据名字搜索
    @Test
    public void test5(){
        List<Student> l = r.findByName("华");
        for (Student s:l){
            System.out.println(s);
        }
    }
    //根据名字或者生日搜索
    @Test
    public void test6(){
        List<Student> list = r.findByNameOrBirthDate("华", "2021-08-29");
        for(Student s:list){
            System.out.println(s);
        }
    }

六 使用 Criteria 构建查询

Spring Data Elasticsearch 中,可以使用 SearchOperations 工具执行一些更复杂的查询,这些查询操作接收一个 Query 对象封装的查询操作。

Spring Data Elasticsearch 中的 Query 有三种:

  • CriteriaQuery
  • StringQuery
  • NativeSearchQuery
     
  • 自定义搜索方法的类
package cn.tedu.es;

import cn.tedu.es.entity.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Component
public class StudentSearcher {
    //执行CriteriaQuery 查询的工具对象
    @Autowired
    private ElasticsearchOperations o;

    // 任意定义的搜索方法
    public List<Student> findByName(String nameKey){
        Criteria c = new Criteria("name");
        c.is(nameKey);
        return exec(c);
    }

    public List<Student> findByBirthDate(String from,String to){
        Criteria c = new Criteria("birthDate");
        c.between(from,to);
        return exec(c);
    }

    private List<Student> exec(Criteria c) {
        // 把条件,封装到一个查询对象
        CriteriaQuery q = new CriteriaQuery(c);
        // 集合SearchHit[SearchHit<Student>, SearchHit<Student>, SearchHit<Student>...]
        SearchHits<Student> shs = o.search(q, Student.class);

//        List<Student> list =new ArrayList<>();
//        for (SearchHit<Student> sh : shs) {
//            Student s = sh.getContent();
//            // 从SearchHit集合中取出的SearchHit<Student>放入list集合中
//            list.add(s);
//        }

        // 可以使用集合的 Stream api 和 lambda 语法, 简化上面的代码
        // 映射出SearchHit集合的数据,放到另一个集合中
        List<Student> list = shs.stream().map(SearchHit::getContent).collect(Collectors.toList());
        return  list;
    }
}
  • 测试类实现自定义方法
package cn.tedu.es;

import cn.tedu.es.entity.Student;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class Test2 {
    @Autowired
    private StudentSearcher s;

    @Test
    public void test1() {
        List<Student> list = s.findByName("华");
        for (Student s : list) {
            System.out.println(s);
        }
    }

    @Test
    public void test2() {
        List<Student> list = s.findByBirthDate("2000-01-01", "2030-01-01");
        for (Student s : list) {
            System.out.println(s);
        }
    }
}

七 Spring Data ES 分页操作

根据关键词搜索出来很多结果,会对结果进行分页

  • Pageable -- 封装向服务器提交的分页参数
  • Page -- 封装从服务器返回的搜索结果的一页数据,和分页属性数据
  • Repository api 
List<Student> findByName(String nameKey, Pageable pageable)
...

Pageable p =PageRequest.of(0,20);
r.finfByName("华,)
  • 接口创建查询并分页方法

  • Test1测试类实现这个方法
   //根据名字搜索并分页
    @Test
    public void test5(){
        Pageable p = PageRequest.of(0, 20);//第一页开始,每页20条数据
        Page<Student> list = r.findByName("华", p);
        for (Student s:list){
            System.out.println(s);
        }
        System.out.println(list.getNumber());
        System.out.println(list.hasPrevious());
        System.out.println(list.hasNext());
        System.out.println(list.getTotalPages());
        System.out.println(list.getSize());
    }

 八 pd-web项目

  • Open Recent 打开位置:Rabbitmq中
  • 启动他的启动类并配置,然后重启,访问localhost:80  进入拼多商城页面

 

 拼多商城搜索条

http://localhost/search/toSearch.html?key=%E6%89%8B%E6%9C%BA

1. pom.xml 添加 es 依赖

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

2.application.yml 添加连接地址

spring
....
   elasticsearch:
    rest:
      uris: 
        - http://192.168.64.181:9200
        - http://192.168.64.181:9201
        - http://192.168.64.181:9202

3.添加实体类Item.封装从es 服务器搜索的结果

package com.pd.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;

@Document(indexName = "pditems") // 指定创建的索引
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Item {
    @Id
    private Long id;
    private String brand;
    private String title;
    @Field("sell_point")//因为这个字段有下划线,需要进行指定
    private String sellPoint;
    private String price;
    private String image;
}

4.创建es包添加 ItemRepository 接口和搜索以及分页的方法: findByTitleOrSellPoint()

package com.pd.es;

import com.pd.pojo.Item;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

public interface ItemRepository
         extends ElasticsearchRepository<Item,Long> {//对象类型,数据类型
    //方法名必须要按格式书写
    Page<Item> findByTitleOrSellPoint(String key1, String key2, Pageable pageable);
}

5.SearchService接口 以及 实现类

package com.pd.service;

import com.pd.pojo.Item;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface SearchService {
    Page<Item> search(String key, Pageable pageable);
}
package com.pd.service.impl;

import com.pd.es.ItemRepository;
import com.pd.pojo.Item;
import com.pd.service.SearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

@Service
public class SearchServiceImpl implements SearchService {
    @Autowired
    private ItemRepository r;

    @Override
    public Page<Item> search(String key, Pageable pageable) {
        return r.findByTitleOrSellPoint(key,key,pageable); //pageable分页参数
    }
}

6.SearchController

package com.pd.controller;

import com.pd.pojo.Item;
import com.pd.service.SearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class SearchController {
    @Autowired
    private SearchService searchService;
    //.../search/toSearch.html?key=手机&page=1&size=20
    @GetMapping("/search/toSearch.html")
    public String search(Model model, String key, Pageable pageable){
        // Model 是用来向界面传递数据工具
        Page<Item> page =
                searchService.search(key, pageable);//搜索结果
        model.addAttribute("page",page);//结果放到model对象中

        return "/search.jsp"; //在这里面显示
    }
}

7.webapp包下Search jsp

8.重启启动类,在搜索栏搜索"手机",即可搜到关于此产品的信息

9.search.jsp添加上一页下一页功能

  • 重启测试:

 

 10. 搜索框回显搜索的关键词

webapp-->commons-->headers.jsp

  •  重启测试:

11 搜索后关键词的高亮显示

  • 商品存储ItemRepository接口中添加高亮显示的注解

注意:

  • SearchHit对象会报错,根据提示的错误找到对象或接口,挨个全部改为SearchHit即可
  • Re'pository api 高亮显示结果 SearchHit , 只能用List 集合存放, 不能用Page,否则会缺少分页属性.

第一步:ItemRepository

package com.pd.es;

import com.pd.pojo.Item;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.annotations.Highlight;
import org.springframework.data.elasticsearch.annotations.HighlightField;
import org.springframework.data.elasticsearch.annotations.HighlightParameters;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import java.util.List;

public interface ItemRepository extends ElasticsearchRepository<Item, Long> {
    @Highlight(
            parameters = @HighlightParameters(
                    preTags = "<em>",
                    postTags = "</em>"
            ),
            fields = @HighlightField(name = "title")
    )
    List<SearchHit<Item>> findByTitleOrSellPoint(String k1, String k2, Pageable pageable);
}

第二步:SearchService接口以及实现类

package com.pd.service;

import com.pd.pojo.Item;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.SearchHit;

import java.util.List;

public interface SearchService {
    List<SearchHit<Item>> search(String key, Pageable pageable);
}
package com.pd.service.impl;

import com.pd.es.ItemRepository;
import com.pd.pojo.Item;
import com.pd.service.SearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class SearchServiceImpl implements SearchService {
    @Autowired
    private ItemRepository r;

    @Override
    public List<SearchHit<Item>> search(String key, Pageable pageable) {
        return r.findByTitleOrSellPoint(key,key,pageable);
    }
}

第三步:SearchController

  • 商品数据的title 设置成高亮结果
  • 所有商品放入List<Item>集合
package com.pd.controller;

import com.pd.pojo.Item;
import com.pd.service.SearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.ArrayList;
import java.util.List;

@Controller
public class SearchController {

    @Autowired
    private SearchService searchService;

    @GetMapping("/search/toSearch.html")
    public String search(Model model, String key, Pageable pageable){
        List<SearchHit<Item>> list = searchService.search(key, pageable);
        List<Item>items = new ArrayList<>();
        for (SearchHit<Item> sh : list) {
            Item it = sh.getContent();
            String title=hlTitle(sh.getHighlightField("title"));
            it.setTitle(title);
            items.add(it);
        }
        model.addAttribute("items",items);
        model.addAttribute("page",pageable);
      // pageable.getPageSize();
      //  pageable.getPageNumber();
        return "/search.jsp";
    }

    private String hlTitle(List<String> title) {
        StringBuilder sb = new StringBuilder();
        for (String s : title) {
            sb.append(s);
        }
        return sb.toString();
    }
}

第四步:search.jsp

  •  设置高亮效果:

  •  重启启动类,搜索"电脑",所有结果中电脑这个词显示红色,并且可以使用上一页下一页功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>