Java微服务篇3——Lucene
1、数据分类
1.1、结构化数据
具有固定格式或有限长度的数据,如数据库,元数据等
常见的结构化数据也就是数据库中的数据,在数据库中搜索很容易实现,通常都是使用 sql语句进行查询,而且能很快的得到查询结果
数据库中的数据存储是有规律的,有行有列而且数据格式、数据长度都是固定的,所以搜索很容易
1.2、非结构化数据
不定长或无固定格式的数据,如邮件,word 文档等磁盘上的文件
1.2.1、顺序扫描
顺序扫描,比如要找内容包含某一个字符串的文件,就是一个文档一个文档的看,对于每一个文 档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫 描完所有的文件。如利用 windows 的搜索也可以搜索文件内容,只是相当的慢
1.2.2、全文检索
全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在 文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果 反馈给用户的检索方法。这个过程类似于通过字典的目录查字的过程
2、全文检索(Lucene)
Lucene 是 apache 下的一个开放源代码的全文检索引擎工具包。提 供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言),Lucene 的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能。
2.1、Lucene优点
稳定、索引性能高
- 每小时能够索引150GB以上的数据
- 对内存的要求小,只需要1MB的堆内存
- 增量索引和批量索引一样快
- 索引的大小约为索引文本大小的20%~30%
高效、准确、高性能的搜索算法
- 良好的搜索排序
- 强大的查询方式支持:短语查询、通配符查询、临近查询、范围查询等
- 支持字段搜索(如标题、作者、内容) 可根据任意字段排序
- 支持多个索引查询结果合并
- 支持更新操作和查询操作同时进行
- 支持高亮、join、分组结果功能
- 速度快
- 可扩展排序模块,内置包含向量空间模型、BM25模型可选
- 可配置存储引擎
跨平台
- 纯java编写
- 作为Apache开源许可下的开源项目,你可以在商业或开源项目中使用
- Lucene有多种语言实现版(如C,C++、Python等),不仅仅是JAVA
2.2、架构图
2.3、Lucene实现全文检索流程
2.4、应用场景
单机软件的搜索:word、markdown
站内搜索:京东、淘宝、拉勾,索引源是数据库
搜索引擎:百度、Google,索引源是爬虫程序抓取的数据
3、Lucene实战
3.1、项目搭建
job_info.sql文件 百度云:https://pan.baidu.com/s/1Iw7Hfd4kHSVptDKdQ2bmaQ提取码:m27x
导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-core -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>4.10.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-analyzers-common -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>4.10.3</version>
</dependency>
</dependencies>
实体类
public class JobInfo {
private Long id;
private String company_name;
private String company_addr;
private String company_info;
private String job_name;
private String job_addr;
private String job_info;
private int salary_min;
private int salary_max;
private String url;
private String time;
}
mapper
@Mapper
public interface JobInfoMapper {
@Select("select * from job_info")
public List<JobInfo> selectJobInfo();
}
service
public interface JobInfoService {
public List<JobInfo> selectJobInfo();
}
@Service
public class JobInfoServiceImpl implements JobInfoService {
@Autowired
JobInfoMapper jobInfoMapper;
@Override
public List<JobInfo> selectJobInfo() {
return jobInfoMapper.selectJobInfo();
}
}
controller
@RestController
public class JobInfoController {
@Autowired
JobInfoServiceImpl jobInfoService;
@RequestMapping("/")
public String hello(){
return "hello,lucene!";
}
@RequestMapping("/selectJobInfo")
public List<JobInfo> selectJobInfo(){
return jobInfoService.selectJobInfo();
}
}
application.yaml
mybatis:
type-aliases-package: cn.winkto.bean
mapper-locations: classpath:mapper/*.xml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: blingbling123.
url: jdbc:mysql://localhost:3306/job?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
application:
name: product
server:
port: 8099
启动类
@SpringBootApplication
@MapperScan("cn.winkto.mapper")
public class LuceneApplication {
public static void main(String[] args) {
SpringApplication.run(LuceneApplication.class, args);
}
}
3.2、Filed类型
Field类型 | 数据类型 | 是否分词 | 是否索引 | 是否存储 | 说明 |
---|---|---|---|---|---|
StringField(FieldName, FieldValue, Store.YES) | 字符串 | N | Y | Y/N | 字符串类型Field, 不分词, 作为一个整体进行索引(如: 身份证号, 订单编号), 是否需要存储由Store.YES或Store.NO决定 |
StoredField(FieldName, FieldValue) | 重载方法, 支持多种类型 | N | N | Y | 构建不同类型的Field, 不分词, 不索引, 要存储. (如: 商品图片路径) |
TextField(FieldName, FieldValue, Store.NO) | 文本类型 | Y | Y | Y/N | 文本类型Field, 分词并且索引, 是否需要存储由Store.YES或Store.NO决定 |
3.3、索引创建
@SpringBootTest
class LuceneApplicationTests {
@Autowired
JobInfoServiceImpl jobInfoService;
@Test
void contextLoads() throws IOException {
// 索引文件存储的位置 D:\index
Directory directory= FSDirectory.open(Paths.get("D:\\index"));
// 分词器
StandardAnalyzer standardAnalyzer = new StandardAnalyzer();
// 索引创建配置对象
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(standardAnalyzer);
// 索引创建对象
IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
// 删除已有索引
indexWriter.deleteAll();
// 元数据查询
List<JobInfo> jobInfos = jobInfoService.selectJobInfo();
for (JobInfo jobInfo : jobInfos) {
// 文档对象 import org.apache.lucene.document.*;
Document indexableFields = new Document();
// 添加元数据
indexableFields.add(new StringField("id", String.valueOf(jobInfo.getId()), Field.Store.YES));
indexableFields.add(new TextField("companyName", jobInfo.getCompany_name(), Field.Store.YES));
indexableFields.add(new TextField("companyAddr", jobInfo.getCompany_addr(), Field.Store.YES));
// 添加文档
indexWriter.addDocument(indexableFields);
}
indexWriter.close();
}
}
3.4、索引查询
@Test
void contextLoads1() throws IOException {
// 索引文件存储的位置 D:\index
Directory directory= FSDirectory.open(Paths.get("D:\\index"));
DirectoryReader reader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(reader);
TermQuery termQuery = new TermQuery(new Term("companyName", "北"));
TopDocs search = indexSearcher.search(termQuery, 100);
System.out.println(search.totalHits);
ScoreDoc[] scoreDocs = search.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
int id=scoreDoc.doc;
Document doc = indexSearcher.doc(id);
System.out.println(doc.get("companyName"));
System.out.println("========================");
}
}
3.5、中文分词器
导入依赖
<dependency>
<groupId>com.janeluo</groupId>
<artifactId>ikanalyzer</artifactId>
<version>2012_u6</version>
</dependency>
测试类
@SpringBootTest
class LuceneApplicationTests {
@Autowired
JobInfoServiceImpl jobInfoService;
@Test
void contextLoads() throws IOException {
// 索引文件存储的位置 D:\index
Directory directory= FSDirectory.open(new File("D:\\index"));
// 分词器
// StandardAnalyzer standardAnalyzer = new StandardAnalyzer();
IKAnalyzer standardAnalyzer = new IKAnalyzer();
// 索引创建配置对象
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LATEST,standardAnalyzer);
// 索引创建对象
IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
// 删除已有索引
indexWriter.deleteAll();
// 元数据查询
List<JobInfo> jobInfos = jobInfoService.selectJobInfo();
for (JobInfo jobInfo : jobInfos) {
// 文档对象 import org.apache.lucene.document.*;
Document indexableFields = new Document();
// 添加元数据
indexableFields.add(new StringField("id", String.valueOf(jobInfo.getId()), Field.Store.YES));
indexableFields.add(new TextField("companyName", jobInfo.getCompany_name(), Field.Store.YES));
indexableFields.add(new TextField("companyAddr", jobInfo.getCompany_addr(), Field.Store.YES));
// 添加文档
indexWriter.addDocument(indexableFields);
}
indexWriter.close();
}
@Test
void contextLoads1() throws IOException {
// 索引文件存储的位置 D:\index
Directory directory= FSDirectory.open(new File("D:\\index"));
DirectoryReader reader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(reader);
TermQuery termQuery = new TermQuery(new Term("companyName", "瓜子"));
TopDocs search = indexSearcher.search(termQuery, 100);
System.out.println(search.totalHits);
ScoreDoc[] scoreDocs = search.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
int id=scoreDoc.doc;
Document doc = indexSearcher.doc(id);
System.out.println(doc.get("companyName"));
System.out.println("========================");
}
}
}