稻草问答-用Elasticsearch搜索
稻草问答-用Elasticsearch搜索
1 Elasticsearch搜索引擎
说到数据检索各位一定会想到使用数据库SQL语句检索数据,数据库SQL语句在有数据库索引技术支持情况下检索性能非常好,在数千万数据中进行检索十几个毫秒就能得到结果。但是数据库索引不是万能的,对于全文检索任务就无法自动使用索引了。
举个例子:
SELECT * FROM question WHERE content LIKE ‘%3ava%'
这个SQL的意义是检索content中包含Java的问题数据。SQL查询条件中以%为开头时候,将不会使用索引优化查询,只能对数据进行逐条比较查询。性能不是一般的慢,数据量少的时候性能还可以接受,如果数据量大比如千万级别的数据,查询一次就需要几分钟了。性能难以接受。传统关系数据库是不能用于全文检索的!
全文检索技术就是为了解决数据内容搜索的性能问题而设计的,全文检索服务器封装的专业的分词索引技术,全文检索服务可以在数亿数据中进行内容匹配检索,往往可以秒级时间就能返回结果。
说明:
- 如果进行全字段匹配,关系数据库在数据库索引支持情况下其性能很好;
- 关系数据库全文检索性能差,关系数据库不适合进行全文检索;
- 专业的搜索引擎内部实现了分词索引技术,适合全文检索。
Elasticsearch是目前最流行的全文搜索服务,利用Elasticsearch就可以实现全文检索功能。
1.1 Elasticsearch
Elasticsearch是一个基于 Lucene的搜索服务器,Lucene是核心技术,Elasticsearch是完整的应用,用电脑作为比喻,Lucene就像是芯片,而Elasticsearch就像是用芯片打造的完整电脑,开箱即用!
- 它提供了一个分布式多用户能力的全文搜索引擎,通过集群部署能够提供高并发、高性能、高可用搜索服务;
- 基于REST ful web接口,可以支持任何编程语言调用;
- Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。
安装Elalticsearch,步骤如下:
首先Elalticsearch是用Java语言编写的,需要JDK8 以后的Java环境。请先安装配置Java环境,如果Java环境变量配置不对,很有可能无法启动Elasticsearch程序。
然后下载安装包:
Windows系统下载安装Windows版本、苹果电脑下载Mac版本、Linux系统下载Linux版本。Windows系统下载后释到文件夹:
然后进入bin文件夹,执行elasticsearch.bat,就能启动Elasticsearch程序了:
也可以使用Windows命令启动Elasticsearch:
D:
cd \opt\elasticsearch-7.6.2\bin
elasticsearch.bat
启动后的窗口不要关闭,如果关闭则Elasticsearch就结束运行了:
测试:打开浏览器访问http://localhost:9200,显示信息就是成功安装了。Elasticsearch也有集群部署功能,是运维层面的问题,与应用开发关系不大,这里不做详细阐述。
Linux、Mac系统安装基本一样,下载并且释放到硬盘,然后使用命令启动就行了:Mac系统:
tar -xvf elasticsearch-7.6.2-darwin-x86_64.tar.gz
cd elasticsearch-7.6.2/bin
./elasticsearch
Linux:
tar -xvf elasticsearch-7.6.2-linux-x86_64.tar.gz
cd elasticsearch-7.6.2/bin
./elasticsearch
1.2 使用RESTful工具访问Elasticsearch
首先在straw项目中创建一个创建一 个straw-search模块项目作为搜索功能模块
配置straw-search项目的POM:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>straw</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<artifactId>straw-search</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>straw-search</name>
<description>稻草问答搜素功能</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置straw项目中的模块
<modules>
<module>straw-portal</module>
<module>straw-generator</module>
<module>straw-resource</module>
<module>straw-eureka</module>
<module>straw-gateway</module>
<module>straw-sys</module>
<module>straw-commons</module>
<module>straw-faq</module>
<module>straw-search</module>
</modules>
IDEA提供了REST fulAPI测试工具,使用这个工具可以直观的访问任何REST API,使用步骤如下:
在项目中创建HTTP Request文件:
使用这个文件中发起HTTP请求信息:
这个文件是文本文件,可以保存大量的调试命令,例如:
### 第一个测试
GET http://localhost:9200
### 在ES中创建一个索引
PUT http://localhost:9200/demo_index
### 从ES中删除一个索引
DELETE https://localhost:9200/demo_index
1.3 安装IK分词插件
当一个文档被存储时,ES会使用分词器从文档中提取出若干词元(token) 来支持索引的存储和搜索。
ES内置了很多分词器,但内置的分词器对中文的处理不好。下面通过例子来看内置分词器的处理。在web客户端发起如下的一个REST请求,对英文语句进行分词:
### 英文分词测试
POST http://localhost:9200/_analyze
Content-Type:application/json
{
"text":"Hello World!"
}
结果显示"hello world" 语句被分为两个单词,因为英文天生以空格分隔,自然就以空格来分词,这没有任何问题。
POST http://localhost:9200/_analyze
HTTP/1.1 200 OK
content-type: application/json; charset=UTF-8
{
"tokens": [
{
"token": "hello",
"start_offset": 0,
"end_offset": 5,
"type": "<ALPHANUM>",
"position": 0
},
{
"token": "world",
"start_offset": 6,
"end_offset": 11,
"type": "<ALPHANUM>",
"position": 1
}
]
}
Response code: 200 (OK); Time: 287ms; Content length: 179 bytes
下面我们看一个中文的语句例子,请求REST如下:
POST http://localhost:9200/_analyze
Content-Type:application/json
{
"text": "世界你好!"
}
操作完成后,响应内容如下:
POST http://localhost:9200/_analyze
HTTP/1.1 200 OK
content-type: application/json; charset=UTF-8
{
"tokens": [
{
"token": "世",
"start_offset": 0,
"end_offset": 1,
"type": "<IDEOGRAPHIC>",
"position": 0
},
{
"token": "界",
"start_offset": 1,
"end_offset": 2,
"type": "<IDEOGRAPHIC>",
"position": 1
},
{
"token": "你",
"start_offset": 2,
"end_offset": 3,
"type": "<IDEOGRAPHIC>",
"position": 2
},
{
"token": "好",
"start_offset": 3,
"end_offset": 4,
"type": "<IDEOGRAPHIC>",
"position": 3
}
]
}
Response code: 200 (OK); Time: 222ms; Content length: 340 bytes
从结果可以看出,这种分词把每个汉字都独立分开来了,这对中文分词就没有意义了,所以ES默认的分词器对中文处理是有问题的。好在有很多不错的第三方的中文分词器,可以很好地和ES结合起来使用。在ES中,每种分词器(包括内置的、第三方的)都会有个名称。上面默认的操作,其实用的分词器的名称是standard。
### 中文分词测试
POST http://localhost:9200/_analyze
Content-Type:application/json
{
"analyzer": "standard",
"text": "世界你好!"
}
当我们换一个分词器处理分词时,只需将”analyzer"字段设置相应的分词器名称即可。ES通过安装插件的方式来支持第三方分词器,对于第三方的中文分词器,比较常用的是中科院ICTCLAS的smartcn和IKAnanlyzer分词器。我们介绍IKAnanlyzer分词器(下面简称ik)的使用
首先需要下载:
然后在Elasticsearch的plugin文件夹中创建一个子文件夹ik, 将下载的文件elasticsearch- analysis-ik-7.8.0.zip释放并且复制到这个文件夹:
重新启动就安装好插件了。Linux、Mac系统参考命令:
cd elasticsearch-7. 6.2\plugin
mkdir ik
cd ik
unzip -/elasticsearch- analysis-ik-7.6.2.zip
Linux、 Mac系统中也需要重新启动Elasticsearch.检查ik分词功能: ik提供了两个分词器,分别是ik. max _ word和ik_ smart,下面我们分别测试下。
先测试ik_smart,输入命令如下:
### 中文分词测试, ik分词器测试, ik_smart ik_max_word
POST http://localhost:9200/_analyze
Content-Type:application/json
{
"analyzer": "ik_smart",
"text": "世界你好!"
}
测试结果:
POST http://localhost:9200/_analyze
HTTP/1.1 200 OK
content-type: application/json; charset=UTF-8
{
"tokens": [
{
"token": "世界",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "你好",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 1
}
]
}
Response code: 200 (OK); Time: 202ms; Content length: 166 bytes
再测试ik_max_word
### 中文分词测试, ik分词器测试, ik_smart ik_max_word
POST http://localhost:9200/_analyze
Content-Type:application/json
{
"analyzer": "ik_max_word",
"text": "世界你好!"
}
测试结果:(效果不明显可自行换text,比如:世界如此之大)
POST http://localhost:9200/_analyze
HTTP/1.1 200 OK
content-type: application/json; charset=UTF-8
{
"tokens": [
{
"token": "世界",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "你好",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 1
}
]
}
Response code: 200 (OK); Time: 181ms; Content length: 166 bytes
比较两个分词器对同一句中文的分词结果 , ik_ max_ word比ik_ smart得到的中文词更多(从两者的英文名含义就可看出来) , 但这样也带来一个问题 ,使用ik_ max _word会占用更多的存储空间。
1.4 使用Elasticsearch进行搜索
使用ES搜索就必须先在ES中建立索引(index) 结构,有了索引结构才能进行搜索。ES 7.的索引结构如下:
- ES服务器节点中可以存储多个index (索引)
- 每个索引中包含多个document (文档)文档的数据结构是JSON格式
使用REST API可以建立ES的存储结构:
首先建立索引questions:
### 创建一个索引
PUT http://localhost:9200/questions
反馈结果,acknowledged的意思 是"确认”:
PUT http://localhost:9200/questions
HTTP/1.1 200 OK
content-type: application/json; charset=UTF-8
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "questions"
}
Response code: 200 (OK); Time: 595ms; Content length: 68 bytes
如果创建错误,可以删除索引:
### 删除一个索引
DELETE http://localhost:9200/questions
为questions索引中的文档属性设置分词器:
### 设置index中的文档属性采用ik分词
### 设定questions索引中文的结构
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"
}
}
}
反馈结果:
HTTP/1.1 200 OK
content-type: application/json; charset=UTF-8
{
"acknowledged": true
}
在questions索引中存储文档,URL 结尾是文档唯-id,id可以是数字或者字符:
### 向ES的questions索引中添加文档数据
POST http://localhost:9200/questions/_create/1
Content-Type: application/json
{
"id":1,
"title": "Java的基本类型有哪些?",
"content": "每次面试的时候都有人问Java的基本类型,为啥呀!"
}
反馈结果,文档的存储位置是/questions/_ doc/1 :
POST http://localhost:9200/questions/_create/1
HTTP/1.1 201 Created
Location: /questions/_doc/1
content-type: application/json; charset=UTF-8
{
"_index": "questions",
"_type": "_doc",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
Response code: 201 (Created); Time: 740ms; Content length: 157 bytes
继续向questions索引中添加文档:
### 继续添加数据
POST http://localhost:9200/questions/_create/2
Content-Type: application/json
{
"id":2,
"title": "Java中int类型的数据范围?",
"content": "int类型的有效范围是啥,为啥要记住这个范围?"
}
### 继续添加数据
POST http://localhost:9200/questions/_create/3
Content-Type: application/json
{
"id":3,
"title": "Java中double类型的数据范围?",
"content": "double类型的有效范围是啥,为啥要记住这个范围?"
}
### 继续添加数据
POST http://localhost:9200/questions/_create/4
Content-Type: application/json
{
"id":4,
"title": "线程创建方式有哪些?",
"content": "如何创建线程,每一种创建方式适合哪些情况?"
}
在questions索引中更新一个文档, 更新文档编号为4的title属性的值:
###更新文档
POST http://localhost:9200/questions/_doc/4/_update
Content-Type:application/json
{
"doc": {
"title": "Java线程创建方式有哪些?"
}
}
删除questions中的一个文档:
DELETE http://localhost:9200/questions/_doc/2
在questions索引中读取一个文档:
### 查询文档
GET http://localhost:9200/questions/_doc/4
反馈结果:
GET http://localhost:9200/questions/_doc/4
HTTP/1.1 200 OK
content-type: application/json; charset=UTF-8
{
"_index": "questions",
"_type": "_doc",
"_id": "4",
"_version": 1,
"_seq_no": 1,
"_primary_term": 1,
"found": true,
"_source": {
"id": 4,
"title": "线程创建方式有哪些?",
"content": "如何创建线程,每一种创建方式适合哪些情况?"
}
}
Response code: 200 (OK); Time: 201ms; Content length: 190 bytes
建立了questions的索引存储结构以后,就可以使用ES进行全 文检索:
### 搜索 ES
POST http://localhost:9200/questions/_search
Content-Type: application/json
{
"query": {"match": {"title": "类型"}}
}
结果:
POST http://localhost:9200/questions/_search
HTTP/1.1 200 OK
content-type: application/json; charset=UTF-8
{
"took": 264,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 0.7290471,
"hits": [
{
"_index": "questions",
"_type": "_doc",
"_id": "1",
"_score": 0.7290471,
"_source": {
"id": 1,
"title": "Java的基本类型有哪些?",
"content": "每次面试的时候都有人问Java的基本类型,为啥呀!"
}
},
{
"_index": "questions",
"_type": "_doc",
"_id": "3",
"_score": 0.6983144,
"_source": {
"id": 3,
"title": "Java中double类型的数据范围?",
"content": "double类型的有效范围是啥,为啥要记住这个范围?"
}
}
]
}
}
Response code: 200 (OK); Time: 283ms; Content length: 493 bytes
多字段搜索:
### 多字段搜索
POST http://localhost:9200/questions/_search
Content-Type: application/json
{
"query": {
"bool": {
"should": [
{"match": {"title": "java类型"}},
{"match": {"content": "java类型"}}
]
}
}
}
结果:
POST http://localhost:9200/questions/_search
HTTP/1.1 200 OK
content-type: application/json; charset=UTF-8
{
"took": 43,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 2.9808133,
"hits": [
{
"_index": "questions",
"_type": "_doc",
"_id": "1",
"_score": 2.9808133,
"_source": {
"id": 1,
"title": "Java的基本类型有哪些?",
"content": "每次面试的时候都有人问Java的基本类型,为啥呀!"
}
},
{
"_index": "questions",
"_type": "_doc",
"_id": "3",
"_score": 1.7646317,
"_source": {
"id": 3,
"title": "Java中double类型的数据范围?",
"content": "double类型的有效范围是啥,为啥要记住这个范围?"
}
}
]
}
}
Response code: 200 (OK); Time: 384ms; Content length: 492 bytes
2 使用SpringBoot整合Elasticsearch
2.1 spring-data-elasticsearch
Elasticsearch提供的Java客户端有一些不太方便的地方 :
- 很多地方需要拼接Json字符串,在java中拼接字符串有多恐怖你应该懂的
- 需要自己把对象序列化为json存储
- 查询到结果也需要自己反序列化为对象
Elasticsearch使用起来稍显繁琐,Spring提供对Spring-Data-Elasticsearch使用非常简洁方便,下面我就采用Spring-Data-Elasticsearch访问Elasticsearch.
Spring家族整合了非常多的软件组件,也提供了对Elasticsearch支持。这个工具是Spring-Data 家族下子项目,其底层封装了Elasticsearch官方提供的客户端程序。
使用Spring-Data-Elasticsearch的第一步,自然是导入包,在straw-search项目中导入包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
导入以后在application.properties中配置参数,包括ES服务器位置,以及日志信息输出配置:
service.port=8099
spring.elasticsearch.rest.uris=http://localhost:9200
logging.level.cn.tedu.straw.search=debug
logging.level.org.elasticsearch.client.RestClient=debug
编写测试案例:
package cn.tedu.straw.search;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import javax.annotation.Resource;
@SpringBootTest
@Slf4j
class StrawSearchApplicationTests {
@Resource
ElasticsearchOperations elasticsearchOperations;
@Test
void contextLoads() {
log.debug("{}",elasticsearchOperations);
}
}
2.2 使用ElasticsearchReposItory
Spring推荐使用ElasticsearchRepository作为数据访问接口。具体用法是,首先定义与ES存储映射的实体类:
public class Item {
Long id;
String title; //标题
String category;// 分类
String brand; //品牌
Double price; //价格
String images; // 图片地址
}
然后通过Spring Data注解来声明字段的映射属性,有下面的三个注解:
- @Document 作用在类,标记实体类为文档对象
- indexName:对应索引库名称
- @ld作用在成员变量,标记一个字段作为id主键
- @Field作用在成员变量,标记为文档的字段,并指定字段映射属性:
- type: 字段类型,取值是枚举: FieldType
- index: 是否索引,布尔类型,默认是true
- store: 是否存储,布尔类型,默认是false
- analyzer: 分词器名称
添加注解以后:
package cn.tedu.straw.search.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
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;
import java.io.Serializable;
@Data
@Slf4j
@Accessors(chain=true)
@NoArgsConstructor //创建无参数构造器
@AllArgsConstructor //创建全部参数构造器
//spring ES API 会自动的在ES中创建items索引
@Document(indexName="items")
public class Item implements Serializable{
@Id
private Long id;
@Field(type= FieldType.Text, analyzer="ik_smart", searchAnalyzer = "ik_smart")
private String title;
@Field(type = FieldType.Keyword)
private String category;
@Field(type = FieldType.Keyword)
private String brand;
@Field(type = FieldType.Keyword)
private String images;
@Field(type = FieldType.Double)
private Double price;
}
注意:FieldType.Text表示这个是文本字段,设置analyzer = "ik_ smart" 表示这个字段会创建分词索引。
@Field(type= FieldType.Keyword), 表示这个字段整体作为一个查询关键字,不进行分词
@Field(index= false) 表示这个列不参与构建索引,不会占有索引空间,不会被索引,用于参与查询的属性。
然后定义访问接口:
@Repository
public interface ItemRepository extends ElasticsearchRepository<Item, Long> {
}
Spring Data的强大之处,就在于你不用写任何数据层处理逻辑代码,自动根据方法名或类的信息进行CRUD操作。只要你定义一个接口,然后继承Repository提供的一些子接口, 就能具备各种基本的CRUD功能。
只需要定义接口,然后继承它就OK了。
继承ElasticsearchRepository接口,Spring会自动提供实现类,还会继承基本的CRUD方法:此时如果创建一个Item对象,调用ItemRepository到保存方法, 数据就会被存储到ES中
@SpringBootTest
@Slf4j
public class ItemRepositoryTests {
@Resource
ItemRepository itemRepository;
@Test
void save(){
Item item=new Item()
.setId(1L)
.setTitle("小米K20手机")
.setBrand("小米")
.setCategory("手机")
.setImage("image/1.png")
.setPrice(2345.00);
Item obj=itemRepository.save(item);
log.debug("{}",obj);
}
}
ElasticsearchRepository不仅能够插入一行数据,还提供了批量存储数据到方法saveAll:
@Test
void saveAll(){
List<Item> list=new ArrayList<>();
list.add(new Item(2L, "小米手机10",
"手机" ,"小米", "2.png", 2000.00));
list.add(new Item(3L, "小米手机9",
"手机" ,"小米", "3.png", 1800.00));
list.add(new Item(4L, "小米手机8",
"手机" ,"小米", "4.png", 1600.00));
list.add(new Item(5L, "小米手机7",
"手机" ,"小米", "5.png", 1500.00));
list.add(new Item(6L, "小米手机6",
"手机" ,"小米", "6.png", 135000.00));
list.add(new Item(7L, "华为手机10",
"手机" ,"华为", "7.png", 3000.00));
list.add(new Item(8L, "华为手机9",
"手机" ,"华为", "8.png", 2800.00));
itemRepository.saveAll(list);
log.debug("OK");
}
ElasticsearchRepository提供了查询全部ltem到方法,利用这个方法可以检验上述添加的结果:
@Test
void findAll(){
Iterable<Item> items=itemRepository.findAll();
items.forEach(item -> log.debug("{}",item));
}
2.3 全文搜索
Spring Data的另一个强大功能,是根据方法名称自动实现功能。
比如:你的方法名叫做: findByTitle, 那么它就知道你是根据title查询,然后自动帮你完成,无需写实现类,这方法底层最终都是执行的Elasticsearch Rest搜索命令。
使用方法自动生成查询功能时候,方法名称要符合一定的约定, IDEA内置的Spring插件支持这种约定,根据开发工具的自动提示就可以拼接出合适的查询。
比如在ItemRepository接口中定义查询,根据查询参数对title进行全文检索:
/**
* 搜索title包含相关关键字的Item对象
* @param title title中的关键字,会被自动进行分词检索
* @return title包含关键字的Item对象
*/
Iterable<Item> queryItemsByTitleMatches(String title);
注意:Spring Data查询方法返回多个数据时候,使用Iterable<元素>作为返回值,Iterable< ltem> 是Java提供的可迭代接口,相当于可迭代集合,可以使用foreach循环和forEach方法进行遍历。根据方法逻辑设计参数个数,这里是一个查询条件title,就设定一个参数title, Spring会自动利用参数查询。
其作用就与如下原生查询作用一样:
POST http://localhost:9200/items/_search
Content-Type: application/json
{
"query": {"match": { "title": "手机” }}
}
测试案例:
@Test
void queryItemsByTitleMatches(){
Iterable<Item> items=itemRepository.queryItemsByTitleMatches("手机");
items.forEach(item -> log.debug("{}",item));
}
再举一个两个查询参数的例子,如果需要查询某个品牌下面的某个商品,则根据参数title和brand进行全文检索: .
/**
* 查询某个品牌下面的全部包含title商品
*/
Iterable<Item> queryItemsByTitleMatchesAndBrandMatches(
String title, String brand
);
这个查询相当于原生的Rest查询:
POST http://localhost:9200/questions/_search
Content-Type: application/json
{
"query": {
"bool": {
"must": [
{"match": {"title": "手机"}},
{"match": {"content": "小米"}}
]
}
}
}
测试案例:
@Test
void queryItemsByTitleMatchesAndBrandMatches(){
Iterable<Item> items=itemRepository.queryItemsByTitleMatchesAndBrandMatches(
"手机","小米"
);
items.forEach(item -> log.debug("{}",item));
}
如果上述结果顺序比较混乱,实际场景中经常希望对查洵结果进行适当的排序,比如如果希望进行按照价格进行排序,则可以定义如下查询, OrderByPriceAsc 的意思就是按照价格降序排列:
/**
* 查询某个品牌下面的全部包含title商品
*/
Iterable<Item> queryItemsByTitleMatchesAndBrandMatchesOrderByPriceAsc(
String title, String brand
);
相当于原生的Rest查询:
POST http://localhost:9200/questions/_search
Content-Type: application/json
{
"query": {
"bool": {
"should": [
{"match": {"title": "手机"}},
{"match": {"brand": "小米"}}
]
}
},"sort":[{"price":"asc"}]
}
测试案例:
@Test
void queryItemsByTitleMatchesAndBrandMatches(){
Iterable<Item> items=itemRepository.queryItemsByTitleMatchesAndBrandMatchesOrderByPriceAsc(
"手机","小米"
);
items.forEach(item -> log.debug("{}",item));
}
对于大量查询结果,经常需要分页显示,Spring-data 提供了分页支持,具体做法是:返回值利用Page<类型>封装;方法参数上增加分页参数Pageable:
/**
*分页查询
*/
Page<Item> queryItemsByTitleMatchesAndBrandMatchesOrderByPriceAsc(
String title, String brand, Pageable pageable
);
测试案例:
@Test
void pageable(){
int pageNum=1; //Spring中的页号从0开始
int pageSize=3; //页面大小,每页显示的行数
Page<Item> page=itemRepository.queryItemsByTitleMatchesAndBrandMatchesOrderByPriceAsc(
"手机","小米", PageRequest.of(pageNum,pageSize)
);
//显示当前页中的数据
page.getContent().forEach(item -> log.debug("{}",item));
//显示分页原数据
log.debug("当前页号:{}",page.getNumber());
log.debug("页面大小:{}",page.getSize());
log.debug("是否是首页:{}",page.isFirst());
log.debug("是否是最后一页:{}",page.isLast());
log.debug("前一页号:{}",page.previousOrFirstPageable().getPageNumber());
log.debug("后一页号:{}",page.nextOrLastPageable().getPageNumber());
}
结果:
request [POST http://localhost:9200/items/_search?pre_filter_shard_size=128&typed_keys=true&max_concurrent_shard_requests=5&ignore_unavailable=false&expand_wildcards=open&allow_no_indices=true&ignore_throttled=true&search_type=dfs_query_then_fetch&batched_reduce_size=512&ccs_minimize_roundtrips=true] returned [HTTP/1.1 200 OK]
2022-04-16 00:25:24.497 DEBUG 11020 --- [ main] c.tedu.straw.search.ItemRepositoryTests : Item(id=2, title=小米手机10, category=手机, brand=小米, image=2.png, price=2000.0)
2022-04-16 00:25:24.498 DEBUG 11020 --- [ main] c.tedu.straw.search.ItemRepositoryTests : Item(id=1, title=小米K20手机, category=手机, brand=小米, image=image/1.png, price=2345.0)
2022-04-16 00:25:24.498 DEBUG 11020 --- [ main] c.tedu.straw.search.ItemRepositoryTests : Item(id=6, title=小米手机6, category=手机, brand=小米, image=6.png, price=135000.0)
2022-04-16 00:25:24.498 DEBUG 11020 --- [ main] c.tedu.straw.search.ItemRepositoryTests : 当前页号:1
2022-04-16 00:25:24.498 DEBUG 11020 --- [ main] c.tedu.straw.search.ItemRepositoryTests : 页面大小:3
2022-04-16 00:25:24.499 DEBUG 11020 --- [ main] c.tedu.straw.search.ItemRepositoryTests : 是否是首页:false
2022-04-16 00:25:24.499 DEBUG 11020 --- [ main] c.tedu.straw.search.ItemRepositoryTests : 是否是最后一页:true
2022-04-16 00:25:24.499 DEBUG 11020 --- [ main] c.tedu.straw.search.ItemRepositoryTests : 前一页号:0
2022-04-16 00:25:24.499 DEBUG 11020 --- [ main] c.tedu.straw.search.ItemRepositoryTests : 后一页号:1
2022-04-16 00:25:24.522 WARN 11020 --- [ntainer#0-0-C-1] org.apache.kafka.clients.NetworkClient : [Consumer clientId=consumer-straw-1, groupId=straw] Connection to node -1 (localhost/127.0.0.1:9092) could not be established. Broker may not be available.
2022-04-16 00:25:24.523 WARN 11020 --- [ntainer#0-0-C-1] org.apache.kafka.clients.NetworkClient : [Consumer clientId=consumer-straw-1, groupId=straw] Bootstrap broker localhost:9092 (id: -1 rack: null) disconnected
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端