全文搜索是对非结构化数据的一种搜索方式,所谓非结构化数据是指相对于结构化数据(如数据库)来说长度不固定或无固定格式的数据,例如文档、邮件等。
对非结构化数据的搜索最常见的方式是顺序扫描法,即对整个文档从头到尾逐字匹配检索,例如Windows的文件搜索或者Linux的grep命令。这种方式适用于数据量较小的文件,当文件量过大时搜索将变得异常缓慢。另一种搜索方式全文搜索对非结构化数据按照一定的索引进行重新组织,从而达到按照索引对数据进行快速搜索的目的。
在Java中常用的全文搜索框架是基于Lucene引擎的ElasticSearch(ES)和Solr,相比于支持多种数据格式的Solr,ES只支持JSON数据,但ES的实时搜索效率较高。它的相关概念和特点如下:
从ElasticSearch官网下载所需版本的压缩包后在本地解压,对于Windows,在解压后的bin目录下找到elasticsearch.bat点击运行,ElasticSearch会在默认的9200端口启动。
在Spring boot中使用ElasticSearch需要引入spring-boot-starter-data-elasticsearch
、jna
两个依赖项
dependencies {
implementation('org.springframework.boot:spring-boot-starter-data-elasticsearch')
implementation('net.java.dev.jna:jna:5.6.0')
......
}
在spring boot的application.properties中配置ElasticSearch如下
# ElasticSearch服务器地址
spring.data.elasticsearch.client.reactive.endpoints=localhost:9200
# 连接超时时间
spring.data.elasticsearch.client.reactive.connection-timeout=120
首先创建Article
类用于表示存储的单个文档(Document)对象,通过注解@Document
表示这是一个文档类。该类需要实现Serializable接口以及满足POJO要求(需要包含空构造函数与字段的getter、setter方法),此外增加一个toString方法用于将其打印字符串。我们可以对Article类的title、summary、content的三个字段进行索引,从而实现快速查找。
package com.tory.blog.entity;
import org.springframework.data.elasticsearch.annotations.Document;
import javax.persistence.Id;
import java.io.Serializable;
@Document(indexName = "article")
public class Article implements Serializable {
@Id
private String id;
private String title;
private String summary;
private String content;
public Article() {
}
public Article(String id, String title, String summary, String content) {
this.id = id;
this.title = title;
this.summary = summary;
this.content = content;
}
//getter & setter...
@Override
public String toString() {
return "Article{" +
"id='" + id + '\'' +
", title='" + title + '\'' +
", summary='" + summary + '\'' +
", content='" + content + '\'' +
'}';
}
}
接着定义ArticleRepository
接口用于操作ElasticSearch,该接口继承自ElasticsearchRepository
。和SpringDataJPA一样,我们只需要在接口中按照规则写好方法名而不必手动去实现,例如这里声明了根据文章标题对文档进行检索findDistinctArticleByTitleContaining()
package com.tory.blog.repository;
import com.tory.blog.entity.Article;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
public interface ArticleRepository extends ElasticsearchRepository<Article, String> {
/**
* 根据Title、Summary、Content分页查询Article
*
* @param title
* @return Page
*/
Page<Article> findDistinctArticleByTitleContaining(String title, Pageable pageable);
}
首先通过articleRepository.save()存入三个文档对象
@Autowired
private ArticleRepository articleRepository;
@BeforeEach
void setUp() {
articleRepository.deleteAll();
articleRepository.save(new Article("1", "静夜思", "李白的诗", "床前明月光,疑是地上霜。"));
articleRepository.save(new Article("2", "青花瓷", "周杰伦的歌", "素胚勾勒出青花笔锋浓转淡。"));
articleRepository.save(new Article("3", "青玉案", "辛弃疾的词", "东风夜放花千树,更吹落星如雨。"));
}
之后在Controller中定义searchArticle()方法,通过调用findDistinctArticleByTitleContaining()
根据title对文档进行搜索,注意该方法返回的是分页之后的查询结果,除了title之外还需要pageable
对象作为参数,通过PageRequest.of()创建。
@RequestMapping("article")
@RestController
public class ArticleController {
@Autowired
private ArticleRepository articleRepository;
@GetMapping("search")
public List<Article> searchArticle(@RequestParam("title") String title){
Pageable pageable= PageRequest.of(0,20); //选择第0页,每页20条结果。
Page<Article> blogPage= articleRepository.findDistinctArticleByTitleContaining(title,pageable);
return blogPage.getContent();
}
}
接着启动项目,访问http://localhost:8080/blog/article/search?title=青,对title含有“青”字的文章进行搜索,返回json结果如下: