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 {@AutowiredJobInfoMapper jobInfoMapper;@Overridepublic List<JobInfo> selectJobInfo() {return jobInfoMapper.selectJobInfo();}
}
controller
@RestController
public class JobInfoController {@AutowiredJobInfoServiceImpl 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.beanmapper-locations: classpath:mapper/*.xml
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: blingbling123.url: jdbc:mysql://localhost:3306/job?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghaiapplication: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 {@AutowiredJobInfoServiceImpl jobInfoService;@Testvoid contextLoads() throws IOException {// 索引文件存储的位置 D:\indexDirectory 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:\indexDirectory 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 {@AutowiredJobInfoServiceImpl jobInfoService;@Testvoid contextLoads() throws IOException {// 索引文件存储的位置 D:\indexDirectory 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();}@Testvoid contextLoads1() throws IOException {// 索引文件存储的位置 D:\indexDirectory 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("========================");}}
}