Lucene
全文检索技术
- 课程计划
- Lucene介绍
- 全文检索流程介绍
- 索引流程
- 搜索流程
- Lucene入门程序
- 索引实现
- 搜索实现
- 分词器
- 分词介绍
- IK分词器
- 搜索技术理论基础
- 为什么要学习Lucene
原来的方式实现搜索功能,我们的搜索流程如下图:
正在上传…重新上传取消
正在上传…重新上传取消
上图就是原始搜索引擎技术,如果用户比较少而且数据库的数据量比较小,那么这种方式实现搜索功能在企业中是比较常见的。
但是数据量过多时,数据库的压力就会变得很大,查询速度会变得非常慢。我们需要使用更好的解决方案来分担数据库的压力。
现在的方案(使用Lucene),如下图
正在上传…重新上传取消
正在上传…重新上传取消
为了解决数据库压力和速度的问题,我们的数据库就变成了索引库,我们使用Lucene的API的来操作服务器上的索引库。这样完全和数据库进行了隔离。
-
- 数据查询方法
- 顺序扫描法
- 数据查询方法
所谓顺序扫描,例如要找内容包含一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫描完所有的文件。
这种方法是顺序扫描方法,数据量大就搜索慢。
-
-
- 倒排索引
-
先举一个栗子:
例如我们使用新华字典查询汉字,新华字典有偏旁部首的目录(索引),我们查字首先查这个目录,找到这个目录中对应的偏旁部首,就可以通过这个目录中的偏旁部首找到这个字所在的位置(文档)。
现在有两篇文档:
Doc1: When in Rome, do as the Romans do.
Doc2: When do you come back from Rome?
Lucene会对以上两篇文档建立倒排索引
索引结构如下图:
正在上传…重新上传取消
- 提取资源中关键信息, 建立索引 (目录)
- 搜索时,根据关键字(目录),找到资源的位置
-
- 搜索技术应用场景
应用场景 :
1、 单机软件的搜索(word中的搜索)
2、 站内搜索 (baidu贴吧、论坛、 京东、 taobao)
3、 垂直领域的搜索 (818工作网) 智联招聘
4、 专业搜索引擎公司 (google、baidu)
- Lucene介绍
- 什么是全文索引(全文检索)
计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式
-
- 什么是Lucene
Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。
Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。
目前已经有很多应用程序的搜索功能是基于 Lucene 的,比如 Eclipse 的帮助系统的搜索功能。Lucene 能够为文本类型的数据建立索引,所以你只要能把你要索引的数据格式转化的文本的,Lucene 就能对你的文档进行索引和搜索。比如你要对一些 HTML 文档,PDF 文档进行索引的话你就首先需要把 HTML 文档和 PDF 文档转化成文本格式的,然后将转化后的内容交给 Lucene 进行索引,然后把创建好的索引文件保存到磁盘或者内存中,最后根据用户输入的查询条件在索引文件上进行查询。不指定要索引的文档的格式也使 Lucene 能够几乎适用于所有的搜索应用程序。
- Lucene是一套用于全文检索和搜寻的开源程式库,由Apache软件基金会支 持和提供
- Lucene提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻, 在Java开发环境里Lucene是一个成熟的免费开放源代码工具
- Lucene并不是现成的搜索引擎产品,但可以用来制作搜索引擎产品
-
- Lucene与搜索引擎的区别
全文检索系统是按照全文检索理论建立起来的用于提供全文检索服务的软件系统,包括建立索引、处理查询返回结果集、增加索引、优化索引结构等功能。例如:百度搜索、eclipse帮助搜索、淘宝网商品搜索等。
搜索引擎是全文检索技术最主要的一个应用,例如百度。搜索引擎起源于传统的信息全文检索理论,即计算机程序通过扫描每一篇文章中的每一个词,建立以词为单位的倒排文件,检索程序根据检索词在每一篇文章中出现的频率和每一个检索词在一篇文章中出现的概率,对包含这些检索词的文章进行排序,最后输出排序的结果。全文检索技术是搜索引擎的核心支撑技术。
Lucene和搜索引擎不同,Lucene是一套用java或其它语言写的全文检索的工具包,为应用程序提供了很多个api接口去调用,可以简单理解为是一套实现全文检索的类库,搜索引擎是一个全文检索系统,它是一个单独运行的软件系统
-
- Lucene官网
官网: http://lucene.apache.org/
正在上传…重新上传取消
- Lucene全文检索的流程
- 索引和搜索流程图
正在上传…重新上传取消
正在上传…重新上传取消
正在上传…重新上传取消
1、绿色表示索引过程,对要搜索的原始内容进行索引构建一个索引库,索引过程包括:
确定原始内容即要搜索的内容à获得文档à创建文档à分析文档à索引文档
2、红色表示搜索过程,从索引库中搜索内容,搜索过程包括:
用户通过搜索界面à创建查询à执行搜索,从索引库搜索à渲染搜索结果
-
- 索引流程
对文档索引的过程,将用户要搜索的文档内容进行索引,索引存储在索引库(index)中。
-
-
- 原始内容
-
原始内容是指要索引和搜索的内容。
原始内容包括互联网上的网页、数据库中的数据、磁盘上的文件等。
-
-
- 获得文档(采集数据)
-
从互联网上、数据库、文件系统中等获取需要搜索的原始信息,这个过程就是信息采集,采集数据的目的是为了对原始内容进行索引。
采集数据分类:
1、对于互联网上网页,可以使用工具将网页抓取到本地生成html文件。
2、数据库中的数据,可以直接连接数据库读取表中的数据。
3、文件系统中的某个文件,可以通过I/O操作读取文件的内容。
在Internet上采集信息的软件通常称为爬虫或蜘蛛,也称为网络机器人,爬虫访问互联网上的每一个网页,将获取到的网页内容存储起来。
Lucene不提供信息采集的类库,需要自己编写一个爬虫程序实现信息采集,也可以通过一些开源软件实现信息采集,如下:
Solr(Welcome to Apache Solr - Apache Solr) ,solr是apache的一个子项目,支持从关系数据库、xml文档中提取原始数据。
Nutch(http://lucene.apache.org/nutch), Nutch是apache的一个子项目,包括大规模爬虫工具,能够抓取和分辨web网站数据。
jsoup(http://jsoup.org/ ),jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。
-
-
- 创建文档
-
获取原始内容的目的是为了索引,在索引前需要将原始内容创建成文档(Document),文档中包括一个一个的域(Field),域中存储内容。
这里我们可以将磁盘上的一个文件当成一个document,Document中包括一些Field,如下图:
Document(文档)
|
Field(域) Name:id(图书id) Value:1 |
Field(域) Name:name(图书名称) Value:lucene |
Field(域) Name:price(图书价格) Value:66 |
Field(域) Name:desc(图书描述) Value:lucene是apache的开源项目,是java开发的检索的工具
|
其它Field.。。。。。。。。。。。。 |
注意:每个Document可以有多个Field,不同的Document可以有不同的Field,同一个Document可以有相同的Field(域名和域值都相同)
-
-
- 分析文档
-
将原始内容创建为包含域(Field)的文档(document),需要再对域中的内容进行分析,分析成为一个一个的单词。
比如下边的文档经过分析如下:
原文档内容:
Lucene is a Java full-text search engine. Lucene is not a complete
application, but rather a code library and API that can easily be used
to add search capabilities to applications.
分析后得到的词:
lucene、java、full、search、engine。。。。
-
-
- 索引文档
-
对所有文档分析得出的语汇单元进行索引,索引的目的是为了搜索,最终要实现只搜索被索引的语汇单元从而找到Document(文档)。
创建索引是对语汇单元索引,通过词语找文档,这种索引的结构叫倒排索引结构。
倒排索引结构是根据内容(词汇)找文档,如下图:
正在上传…重新上传取消
倒排索引结构也叫反向索引结构,包括索引和文档两部分,索引即词汇表,它的规模较小,而文档集合较大。
-
- 搜索流程
搜索就是用户输入关键字,从索引中进行搜索的过程。根据关键字搜索索引,根据索引找到对应的文档,从而找到要搜索的内容。
-
-
- 用户
-
就是使用搜索的角色,用户可以是自然人,也可以是远程调用的程序。
-
-
- 用户搜索界面
-
全文检索系统提供用户搜索的界面供用户提交搜索的关键字,搜索完成展示搜索结果。如下图:
正在上传…重新上传取消
Lucene不提供制作用户搜索界面的功能,需要根据自己的需求开发搜索界面。
-
-
- 创建查询
-
用户输入查询关键字执行搜索之前需要先构建一个查询对象,查询对象中可以指定查询要查询关键字、要搜索的Field文档域等,查询对象会生成具体的查询语法,比如:
name:lucene表示要搜索name这个Field域中,内容为“lucene”的文档。
desc:lucene AND desc:java 表示要搜索即包括关键字“lucene”也包括“java”的文档。
-
-
- 执行搜索
-
搜索索引过程:
- 根据查询语法在倒排索引词典表中分别找出对应搜索词的索引,从而找到索引所链接的文档链表。
例如搜索语法为“desc:lucene AND desc:java”表示搜索出的文档中既要包括lucene也要包括java。
词lucene |
文档lucene |
文档solr |
..... |
词java |
文档lucene |
文档java编程思想 |
..... |
- 由于是AND,所以要对包含lucene或java词语的链表进行交集,得到文档链表应该包括每一个搜索词语
3、获取文档中的Field域数据。
-
-
- 渲染结果
-
以一个友好的界面将查询结果展示给用户,用户根据搜索结果找自己想要的信息,为了帮助用户很快找到自己的结果,提供了很多展示的效果,比如搜索结果中将关键字高亮显示,百度提供的快照等。
正在上传…重新上传取消
- Lucene入门
- Lucene准备
Lucene可以在官网上下载。课程已经准备好了Lucene的文件,我们使用的是4.10.3版本,文件位置如下图:
正在上传…重新上传取消
解压后的效果:
正在上传…重新上传取消
使用这三个文件的jar包,就可以实现lucene功能
本教程使用的数据是MySQL数据库的数据,所以还需要MySQL的连接包
学员编写的时候,也可以直接复制准备好的jar包,位置如下图:
正在上传…重新上传取消
-
- 开发环境
JDK: 1.7 (Lucene4.8以上,必须使用JDK1.7及以上版本)
IDE: eclipse Mars2
数据库: MySQL
数据库脚本位置如下图:
正在上传…重新上传取消
导入到MySQL效果如下图:
正在上传…重新上传取消
-
- 创建Java工程
创建java工程测试即可,效果如下:
正在上传…重新上传取消
-
- 索引流程
- 数据采集
- 索引流程
在电商网站中,全文检索的数据源在数据库中,需要通过jdbc访问数据库中book表的内容。
-
-
-
- 创建pojo
-
-
public class Book {
// 图书ID
private Integer id;
// 图书名称
private String name;
// 图书价格
private Float price;
// 图书图片
private String pic;
// 图书描述
private String desc;
get/set。。。
}
-
-
-
- 创建DAO接口
-
-
public interface BookDao {
/**
* 查询所有的book数据
*
* @return
*/
List<Book> queryBookList();
}
-
-
-
- 创建DAO接口实现类
-
-
使用jdbc实现
public class BookDaoImpl implements BookDao {
@Override
public List<Book> queryBookList() {
// 数据库链接
Connection connection = null;
// 预编译statement
PreparedStatement preparedStatement = null;
// 结果集
ResultSet resultSet = null;
// 图书列表
List<Book> list = new ArrayList<Book>();
try {
// 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 连接数据库
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/solr", "root", "root");
// SQL语句
String sql = "SELECT * FROM book";
// 创建preparedStatement
preparedStatement = connection.prepareStatement(sql);
// 获取结果集
resultSet = preparedStatement.executeQuery();
// 结果集解析
while (resultSet.next()) {
Book book = new Book();
book.setId(resultSet.getInt("id"));
book.setName(resultSet.getString("name"));
book.setPrice(resultSet.getFloat("price"));
book.setPic(resultSet.getString("pic"));
book.setDesc(resultSet.getString("desc"));
list.add(book);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
}
-
-
- 实现索引流程
-
- 采集数据
- 创建Document文档对象
- 创建分析器(分词器)
- 创建IndexWriterConfig配置信息类
- 创建Directory对象,声明索引库存储位置
- 创建IndexWriter写入对象
- 把Document写入到索引库中
- 释放资源
public class CreateIndexTest {
@Test
public void testCreateIndex() throws Exception {
// 1. 采集数据
BookDao bookDao = new BookDaoImpl();
List<Book> bookList = bookDao.queryBookList();
// 2. 创建Document文档对象
List<Document> documents = new ArrayList<>();
for (Book book : bookList) {
Document document = new Document();
// Document文档中添加Field域
// 图书Id
// Store.YES:表示存储到文档域中
document.add(new TextField("id", book.getId().toString(), Store.YES));
// 图书名称
document.add(new TextField("name", book.getName().toString(), Store.YES));
// 图书价格
document.add(new TextField("price", book.getPrice().toString(), Store.YES));
// 图书图片地址
document.add(new TextField("pic", book.getPic().toString(), Store.YES));
// 图书描述
document.add(new TextField("desc", book.getDesc().toString(), Store.YES));
// 把Document放到list中
documents.add(document);
}
// 3. 创建Analyzer分词器,分析文档,对文档进行分词
Analyzer analyzer = new StandardAnalyzer();
// 4. 创建Directory对象,声明索引库的位置
Directory directory = FSDirectory.open(new File("C:/itcast/lucene/index"));
// 5. 创建IndexWriteConfig对象,写入索引需要的配置
IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);
// 6.创建IndexWriter写入对象
IndexWriter indexWriter = new IndexWriter(directory, config);
// 7.写入到索引库,通过IndexWriter添加文档对象document
for (Document doc : documents) {
indexWriter.addDocument(doc);
}
// 8.释放资源
indexWriter.close();
}
}
执行效果:
在文件夹中出现了以下文件,表示创建索引成功
正在上传…重新上传取消
-
-
- 使用Luke查看索引
-
Luke作为Lucene工具包中的一个工具(http://www.getopt.org/luke/),可以通过界面来进行索引文件的查询、修改
luke所在位置如下图:
正在上传…重新上传取消
打开Luke方法:打开cmd命令行运行命令:java -jar lukeall-4.10.3.jar
打开后,使用如下图:
正在上传…重新上传取消
下图是索引域的展示效果:
正在上传…重新上传取消
下图是文档域展示效果
正在上传…重新上传取消
-
- 搜索流程
- 输入查询语句
- 搜索流程
Lucene可以通过query对象输入查询语句。同数据库的sql一样,lucene也有固定的查询语法:
最基本的有比如:AND, OR, NOT 等(必须大写)
举个栗子:
用户想找一个desc中包括java关键字和lucene关键字的文档。
它对应的查询语句:desc:java AND desc:lucene
如下图是使用luke搜索的例子:
正在上传…重新上传取消
-
-
-
- 搜索分词
-
-
和索引过程的分词一样,这里要对用户输入的关键字进行分词,一般情况索引和搜索使用的分词器一致。
比如:输入搜索关键字“java学习”,分词后为java和学习两个词,与java和学习有关的内容都搜索出来了,如下:
正在上传…重新上传取消
-
-
- 代码实现
-
1. 创建Query搜索对象
2. 创建Directory流对象,声明索引库位置
3. 创建索引读取对象IndexReader
4. 创建索引搜索对象IndexSearcher
5. 使用索引搜索对象,执行搜索,返回结果集TopDocs
6. 解析结果集
7. 释放资源
IndexSearcher搜索方法如下:
方法 | 说明 |
indexSearcher.search(query, n) | 根据Query搜索,返回评分最高的n条记录 |
indexSearcher.search(query, filter, n) | 根据Query搜索,添加过滤策略,返回评分最高的n条记录 |
indexSearcher.search(query, n, sort) | 根据Query搜索,添加排序策略,返回评分最高的n条记录 |
indexSearcher.search(booleanQuery, filter, n, sort) | 根据Query搜索,添加过滤策略,添加排序策略,返回评分最高的n条记录 |
代码实现
public class SearchIndexTest {
@Test
public void testSearchIndex() throws Exception {
// 1. 创建Query搜索对象
// 创建分词器
Analyzer analyzer = new StandardAnalyzer();
// 创建搜索解析器,第一个参数:默认Field域,第二个参数:分词器
QueryParser queryParser = new QueryParser("desc", analyzer);
// 创建搜索对象
Query query = queryParser.parse("desc:java AND lucene");
// 2. 创建Directory流对象,声明索引库位置
Directory directory = FSDirectory.open(new File("C:/itcast/lucene/index"));
// 3. 创建索引读取对象IndexReader
IndexReader reader = DirectoryReader.open(directory);
// 4. 创建索引搜索对象
IndexSearcher searcher = new IndexSearcher(reader);
// 5. 使用索引搜索对象,执行搜索,返回结果集TopDocs
// 第一个参数:搜索对象,第二个参数:返回的数据条数,指定查询结果最顶部的n条数据返回
TopDocs topDocs = searcher.search(query, 10);
System.out.println("查询到的数据总条数是:" + topDocs.totalHits);
// 获取查询结果集
ScoreDoc[] docs = topDocs.scoreDocs;
// 6. 解析结果集
for (ScoreDoc scoreDoc : docs) {
// 获取文档
int docID = scoreDoc.doc;
Document doc = searcher.doc(docID);
System.out.println("=============================");
System.out.println("docID:" + docID);
System.out.println("bookId:" + doc.get("id"));
System.out.println("name:" + doc.get("name"));
System.out.println("price:" + doc.get("price"));
System.out.println("pic:" + doc.get("pic"));
// System.out.println("desc:" + doc.get("desc"));
}
// 7. 释放资源
reader.close();
}
}
- 分词器
- 分词理解
在对Docuemnt中的内容进行索引之前,需要使用分词器进行分词 ,分词的目的是为了搜索。分词的主要过程就是先分词后过滤。
- 分词:采集到的数据会存储到document对象的Field域中,分词就是将Document中Field的value值切分成一个一个的词。
- 过滤:包括去除标点符号过滤、去除停用词过滤(的、是、a、an、the等)、大写转小写、词的形还原(复数形式转成单数形参、过去式转成现在式。。。)等。
什么是停用词?停用词是为节省存储空间和提高搜索效率,搜索引擎在索引页面或处理搜索请求时会自动忽略某些字或词,这些字或词即被称为Stop Words(停用词)。比如语气助词、副词、介词、连接词等,通常自身并无明确的意义,只有将其放入一个完整的句子中才有一定作用,如常见的“的”、“在”、“是”、“啊”等。
对于分词来说,不同的语言,分词规则不同。Lucene作为一个工具包提供不同国家的分词器,本例子使用StandardAnalyzer,它可以对用英文进行分词。
如下是org.apache.lucene.analysis.standard.standardAnalyzer的部分源码:
@Override
protected TokenStreamComponents createComponents(final String fieldName, final Reader reader) {
final StandardTokenizer src = new StandardTokenizer(getVersion(), reader);
src.setMaxTokenLength(maxTokenLength);
TokenStream tok = new StandardFilter(getVersion(), src);
tok = new LowerCaseFilter(getVersion(), tok);
tok = new StopFilter(getVersion(), tok, stopwords);
return new TokenStreamComponents(src, tok) {
@Override
protected void setReader(final Reader reader) throws IOException {
src.setMaxTokenLength(StandardAnalyzer.this.maxTokenLength);
super.setReader(reader);
}
};
}
Tokenizer就是分词器,负责将reader转换为语汇单元即进行分词处理,Lucene提供了很多的分词器,也可以使用第三方的分词,比如IKAnalyzer一个中文分词器。
TokenFilter是分词过滤器,负责对语汇单元进行过滤,TokenFilter可以是一个过滤器链儿,Lucene提供了很多的分词器过滤器,比如大小写转换、去除停用词等。
如下图是语汇单元的生成过程:
正在上传…重新上传取消
从一个Reader字符流开始,创建一个基于Reader的Tokenizer分词器,经过三个TokenFilter生成语汇单元Token。
比如下边的文档经过分析器分析如下:
- 原文档内容:
Lucene is a Java full-text search engine. |
- 分析后得到的多个语汇单元:
lucene 、java、full、text、search、engine |
-
- Analyzer使用时机
- 索引时使用Analyzer
- Analyzer使用时机
输入关键字进行搜索,当需要让该关键字与文档域内容所包含的词进行匹配时需要对文档域内容进行分析,需要经过Analyzer分析器处理生成语汇单元(Token)。分析器分析的对象是文档中的Field域。当Field的属性tokenized(是否分词)为true时会对Field值进行分析,如下图:
正在上传…重新上传取消
对于一些Field可以不用分析:
1、不作为查询条件的内容,比如文件路径
2、不是匹配内容中的词而匹配Field的整体内容,比如订单号、身份证号等。
-
-
- 搜索时使用Analyzer
-
对搜索关键字进行分析和索引分析一样,使用Analyzer对搜索关键字进行分析、分词处理,使用分析后每个词语进行搜索。比如:搜索关键字:spring web ,经过分析器进行分词,得出:spring web拿词去索引词典表查找 ,找到索引链接到Document,解析Document内容。
对于匹配整体Field域的查询可以在搜索时不分析,比如根据订单号、身份证号查询等。
注意:搜索使用的分析器要和索引使用的分析器一致。
-
- 中文分词器
- 什么是中文分词器
- 中文分词器
学过英文的都知道,英文是以单词为单位的,单词与单词之间以空格或者逗号句号隔开。所以对于英文,我们可以简单以空格判断某个字符串是否为一个单词,比如I love China,love 和 China很容易被程序区分开来。
而中文则以字为单位,字又组成词,字和词再组成句子。中文“我爱中国”就不一样了,电脑不知道“中国”是一个词语还是“爱中”是一个词语。
把中文的句子切分成有意义的词,就是中文分词,也称切词。我爱中国,分词的结果是:我、爱、中国。
-
-
- Lucene自带中文分词器
-
- StandardAnalyzer:
单字分词:就是按照中文一个字一个字地进行分词。如:“我爱中国”,
效果:“我”、“爱”、“中”、“国”。
- CJKAnalyzer
二分法分词:按两个字进行切分。如:“我是中国人”,效果:“我是”、“是中”、“中国”“国人”。
上边两个分词器无法满足需求。
- SmartChineseAnalyzer
对中文支持较好,但扩展性差,扩展词库,禁用词库和同义词库等不好处理
-
- 第三方中文分词器
- paoding: 庖丁解牛最新版在 https://code.google.com/p/paoding/ 中最多支持Lucene 3.0,且最新提交的代码在 2008-06-03,在svn中最新也是2010年提交,已经过时,不予考虑。
- mmseg4j:最新版已从 https://code.google.com/p/mmseg4j/ 移至 https://github.com/chenlb/mmseg4j-solr,支持Lucene 4.10,且在github中最新提交代码是2014年6月,从09年~14年一共有:18个版本,也就是一年几乎有3个大小版本,有较大的活跃度,用了mmseg算法。
- IK-analyzer: 最新版在https://code.google.com/p/ik-analyzer/上,支持Lucene 4.10从2006年12月推出1.0版开始, IKAnalyzer已经推出了4个大版本。最初,它是以开源项目Luence为应用主体的,结合词典分词和文法分析算法的中文分词组件。从3.0版本开 始,IK发展为面向Java的公用分词组件,独立于Lucene项目,同时提供了对Lucene的默认优化实现。在2012版本中,IK实现了简单的分词 歧义排除算法,标志着IK分词器从单纯的词典分词向模拟语义分词衍化。 但是也就是2012年12月后没有在更新。
- ansj_seg:最新版本在 GitHub - NLPchina/ansj_seg: ansj分词.ict的真正java实现.分词效果速度都超过开源版的ict. 中文分词,人名识别,词性标注,用户自定义词典 tags仅有1.1版本,从2012年到2014年更新了大小6次,但是作者本人在2014年10月10日说明:“可能我以后没有精力来维护ansj_seg了”,现在由”nlp_china”管理。2014年11月有更新。并未说明是否支持Lucene,是一个由CRF(条件随机场)算法所做的分词算法。
- imdict-chinese-analyzer:最新版在 https://code.google.com/p/imdict-chinese-analyzer/ , 最新更新也在2009年5月,下载源码,不支持Lucene 4.10 。是利用HMM(隐马尔科夫链)算法。
- Jcseg:最新版本在git.oschina.net/lionsoul/jcseg,支持Lucene 4.10,作者有较高的活跃度。利用mmseg算法。
-
- 使用中文分词器IKAnalyzer
IKAnalyzer继承Lucene的Analyzer抽象类,使用IKAnalyzer和Lucene自带的分析器方法一样,将Analyzer测试代码改为IKAnalyzer测试中文分词效果。
如果使用中文分词器ik-analyzer,就需要在索引和搜索程序中使用一致的分词器:IK-analyzer。
我是中国人
‘我是’
中国
国人
中国人
-
-
- 添加jar包
-
正在上传…重新上传取消
-
-
- 修改分词器代码
-
@Test
public void testCreateIndex() throws Exception {
// 1. 采集数据
BookDao bookDao = new BookDaoImpl();
List<Book> bookList = bookDao.queryBookList();
// 2. 创建Document文档对象
List<Document> documents = new ArrayList<>();
for (Book book : bookList) {
Document document = new Document();
// Document文档中添加Field域
// 图书Id
// Store.YES:表示存储到文档域中
document.add(new TextField("id", book.getId().toString(), Store.YES));
// 图书名称
document.add(new TextField("name", book.getName().toString(), Store.YES));
// 图书价格
document.add(new TextField("price", book.getPrice().toString(), Store.YES));
// 图书图片地址
document.add(new TextField("pic", book.getPic().toString(), Store.YES));
// 图书描述
document.add(new TextField("desc", book.getDesc().toString(), Store.YES));
// 把Document放到list中
documents.add(document);
}
// 3. 创建Analyzer分词器,分析文档,对文档进行分词
// Analyzer analyzer = new StandardAnalyzer();
Analyzer analyzer = new IKAnalyzer();
// 4. 创建Directory对象,声明索引库的位置
Directory directory = FSDirectory.open(new File("C:/itcast/lucene/index"));
// 5. 创建IndexWriteConfig对象,写入索引需要的配置
IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);
// 6.创建IndexWriter写入对象
IndexWriter indexWriter = new IndexWriter(directory, config);
// 7.写入到索引库,通过IndexWriter添加文档对象document
for (Document doc : documents) {
indexWriter.addDocument(doc);
}
// 8.释放资源
indexWriter.close();
}
-
- 扩展中文词库
如果想配置扩展词和停用词,就创建扩展词的文件和停用词的文件。
注意:不要用window自带的记事本保存扩展词文件和停用词文件,那样的话,格式中是含有bom的。
正在上传…重新上传取消
从ikanalyzer包中拷贝配置文件
正在上传…重新上传取消
拷贝到资源文件夹中
正在上传…重新上传取消
IKAnalyzer.cfg.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">ext.dic;</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">stopword.dic;</entry>
</properties>
中文词库,添加新词的地方
正在上传…重新上传取消
stopword.dic是存放停用词的地方
正在上传…重新上传取消
最终分词效果
正在上传…重新上传取消
- Lucene 理论篇
- 为什么要用Lucene?
- 在搜索的时候完全与数据库隔离,降低数据库压力,提高搜索效率
- 里面的数据查询的方式
- 顺序扫描法
- 先搜索文档,再根据文档中的关键字查询
- 倒排索引法
- 先查找关键字,然后根据关键字再找到文档(坐标)
- 顺序扫描法
- 为什么要用Lucene?
- Lucene介绍
- 啥是Lucene?
- Apache组织下的开源的搜索引擎工具包(jar)
- 不能单独在Tomcat下运行
- 全文索引(全文检索)
- 先查找关键字再把关键字指定坐标包括着文档的编号生成索引,然后在对索引进行搜索的过程叫做全文检索
- 搜索引擎
- 搜索系统,产品;它能在服务器下单独运行
- Apache组织下的开源的搜索引擎工具包(jar)
- 啥是Lucene?
- Lucene索引和搜索流程
- 索引流程
- 采集数据(数据:数据库,文档,WEB页面)——>创建文档对象[里面的存储结构是Field域(Map<域名:域值>)]存放数据——>分析文档(对文档中的文章进行分词【目的:创建索引】)——>写入索引库(索引Map<域名:Map<域值:坐标>>和文档集合形式)
- 搜索流程
- 创建搜索解析器QueryParser——>根据解析器创建查询对象Query——>指定索引库——>读取这个索引库——>根据读取到的对象创建搜索对象indexSearch【索引和文档】——>根据这个查询条件对象来搜索返回TopDocs【索引和坐标集合数组形式的】——>遍历这个数组获取里面的文档ID——>根据ID查询文档Document——>打印文档结果
- 索引流程
- 代码实现
- 分词器的使用
- 为了分析文档给分词用的
- 默认是国外的分词器不适合中文使用
- IKAnalyzer 中文分词器
- 怎么用?
- 分词器的Jar
- 分词器的核心配置文件
- 扩展词典:自定义词语
- 停用词典:忽略的分词
- 直接 new
- 怎么用?
1、课程计划
- Lucene的Field——TextField
- Lucene的索引库维护:增 删改
- lucene的查询
- Query子对象
- QueryParser
- Lucene相关度排序
- solr介绍:Apache下的开源 搜索引擎系统
- solr安装配置
- Solrj的使用
- Field域
- Field属性
Field是文档中的域,包括Field名和Field值两部分,一个文档可以包括多个Field,Document只是Field的一个承载体,Field值即为要索引的内容,也是要搜索的内容。
- 是否分词(tokenized)
是:作分词处理,即将Field值进行分词,分词的目的是为了索引。
比如:商品名称、商品描述等,这些内容用户要输入关键字搜索,由于搜索的内容格式大、内容多需要分词后将语汇单元建立索引
否:不作分词处理
比如:商品id、订单号、身份证号等
- 是否索引(indexed)
是:进行索引。将Field分词后的词或整个Field值进行索引,存储到索引域,索引的目的是为了搜索。
比如:商品名称、商品描述分析后进行索引,订单号、身份证号不用分词但也要索引,这些将来都要作为查询条件。
否:不索引。
比如:图片路径、文件路径等,不用作为查询条件的不用索引。
- 是否存储(stored)
是:将Field值存储在文档域中,存储在文档域中的Field才可以从Document中获取。
比如:商品名称、订单号,凡是将来要从Document中获取的Field都要存储。
否:不存储Field值
比如:商品描述,内容较大不用存储。如果要向用户展示商品描述可以从系统的关系数据库中获取。
-
- Field常用类型
下边列出了开发中常用 的Filed类型,注意Field的属性,根据需求选择:
Field类 | 数据类型 | Analyzed 是否分词 | Indexed 是否索引 | Stored 是否存储 | 说明 |
StringField(FieldName, FieldValue,Store.YES)) | 字符串 | N | Y | Y或N | 这个Field用来构建一个字符串Field,但是不会进行分词,会将整个串存储在索引中,比如(订单号,身份证号等) 是否存储在文档中用Store.YES或Store.NO决定 |
LongField(FieldName, FieldValue,Store.YES) FloatField | Long型 | Y | Y | Y或N | 这个Field用来构建一个Long数字型Field,进行分词和索引,比如(价格) 是否存储在文档中用Store.YES或Store.NO决定 |
StoredField(FieldName, FieldValue) | 重载方法,支持多种类型 | N | N | Y | 这个Field用来构建不同类型Field 不分析,不索引,但要Field存储在文档中 |
TextField(FieldName, FieldValue, Store.NO) 或 TextField(FieldName, reader) | 字符串 或 流 | Y | Y | Y或N | 如果是一个Reader, lucene猜测内容比较多,会采用Unstored的策略. |
-
- Field修改
- 修改分析
- Field修改
图书id:
是否分词:不用分词,因为不会根据商品id来搜索商品
是否索引:不索引,因为不需要根据图书ID进行搜索
是否存储:要存储,因为查询结果页面需要使用id这个值。
图书名称:
是否分词:要分词,因为要根据图书名称的关键词搜索。
是否索引:要索引。
是否存储:要存储。
图书价格:
是否分词:要分词,lucene对数字型的值只要有搜索需求的都要分词和索引,因 为lucene对数字型的内容要特殊分词处理,需要分词和索引。
是否索引:要索引
是否存储:要存储
图书图片地址:
是否分词:不分词
是否索引:不索引
是否存储:要存储
图书描述:
是否分词:要分词
是否索引:要索引
是否存储:因为图书描述内容量大,不在查询结果页面直接显示,不存储。
不存储是不在lucene的索引域中记录,节省lucene的索引文件空间。
如果要在详情页面显示描述,解决方案:
从lucene中取出图书的id,根据图书的id查询关系数据库(MySQL)中book表得到描述信息。
-
-
- 代码修改
-
对之前编写的testCreateIndex()方法进行修改。
代码片段
// Document文档中添加域
// 图书Id
// Store.YES:表示存储到文档域中
// 不分词,不索引,储存
document.add(new StoredField("id", book.getId().toString()));
// 图书名称
// 分词,索引,储存
document.add(new TextField("name", book.getName().toString(), Store.YES));
// 图书价格
// 分词,索引,储存
document.add(new FloatField("price", book.getPrice(), Store.YES));
// 图书图片地址
// 不分词,不索引,储存
document.add(new StoredField("pic", book.getPic().toString()));
// 图书描述
// 分词,索引,不储存
document.add(new TextField("desc", book.getDesc().toString(), Store.NO));
- 索引维护
- 需求
管理人员通过电商系统更改图书信息,这时更新的是关系数据库,如果使用lucene搜索图书信息,需要在数据库表book信息变化时及时更新lucene索引库。
-
- 添加索引
调用 indexWriter.addDocument(doc)添加索引。
参考入门程序的创建索引。
-
- 删除索引
- 删除指定索引
- 删除索引
根据Term项删除索引,满足条件的将全部删除。
@Test
public void testIndexDelete() throws Exception {
// 创建Directory流对象
Directory directory = FSDirectory.open(new File("C:/itcast/lucene/index"));
IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, null);
// 创建写入对象
IndexWriter indexWriter = new IndexWriter(directory, config);
// 根据Term删除索引库,name:java
indexWriter.deleteDocuments(new Term("name", "java"));
// 释放资源
indexWriter.close();
}
效果如下图:索引域没有变化
正在上传…重新上传取消
文档域数据被删除掉
正在上传…重新上传取消
正在上传…重新上传取消
-
-
- 删除全部索引(慎用)
-
将索引目录的索引信息全部删除,直接彻底删除,无法恢复。
建议参照关系数据库基于主键删除方式,所以在创建索引时需要创建一个主键Field,删除时根据此主键Field删除。
索引删除后将放在Lucene的回收站中,Lucene3.X版本可以恢复删除的文档,3.X之后无法恢复。
代码:
@Test
public void testIndexDelete() throws Exception {
// 创建Directory流对象
Directory directory = FSDirectory.open(new File("D:/itcast/lucene/index"));
IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, null);
// 创建写入对象
IndexWriter indexWriter = new IndexWriter(directory, config);
// 根据Term删除索引库,name:java
// indexWriter.deleteDocuments(new Term("name", "java"));
// 全部删除
indexWriter.deleteAll();
// 释放资源
indexWriter.close();
}
索引域数据清空
正在上传…重新上传取消
文档域数据也清空
正在上传…重新上传取消
-
- 修改索引
更新索引是先删除再添加,建议对更新需求采用此方法并且要保证对已存在的索引执行更新,可以先查询出来,确定更新记录存在执行更新操作。
如果更新索引的目标文档对象不存在,则执行添加。
代码
@Test
public void testIndexUpdate() throws Exception {
// 创建分词器
Analyzer analyzer = new IKAnalyzer();
// 创建Directory流对象
Directory directory = FSDirectory.open(new File("C:/itcast/lucene/index"));
IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);
// 创建写入对象
IndexWriter indexWriter = new IndexWriter(directory, config);
// 创建Document
Document document = new Document();
document.add(new TextField("id", "1002", Store.YES));
document.add(new TextField("name", "lucene测试test 002", Store.YES));
// 执行更新,会把所有符合条件的Document删除,再新增。
indexWriter.updateDocument(new Term("name", "test"), document);
// 释放资源
indexWriter.close();
}
- 搜索
- 创建查询的两种方法
对要搜索的信息创建Query查询对象,Lucene会根据Query查询对象生成最终的查询语法。类似关系数据库Sql语法一样,Lucene也有自己的查询语法,比如:“name:lucene”表示查询名字为name的Field域中的“lucene”的文档信息。
可通过两种方法创建查询对象:
1)使用Lucene提供Query子类
Query是一个抽象类,lucene提供了很多查询对象,比如TermQuery项精确查询,NumericRangeQuery数字范围查询等。
如下代码:
Query query = new TermQuery(new Term("name", "lucene"));
2)使用QueryParse解析查询表达式
QueryParser会将用户输入的查询表达式解析成Query对象实例。
如下代码:
QueryParser queryParser = new QueryParser("name", new IKAnalyzer());
Query query = queryParser.parse("name:lucene");
-
- 通过Query子类搜索
- TermQuery
- 通过Query子类搜索
TermQuery词项查询,TermQuery不使用分析器,搜索关键词进行精确匹配Field域中的词,比如订单号、分类ID号等。
搜索对象创建:
@Test
public void testSearchTermQuery() throws Exception {
// 创建TermQuery搜索对象
Query query = new TermQuery(new Term("name", "lucene"));
doSearch(query);
}
抽取搜索逻辑:
private void doSearch(Query query) throws IOException {
// 2. 执行搜索,返回结果集
// 创建Directory流对象
Directory directory = FSDirectory.open(new File("D:/itcast/lucene/index"));
// 创建索引读取对象IndexReader
IndexReader reader = DirectoryReader.open(directory);
// 创建索引搜索对象
IndexSearcher searcher = new IndexSearcher(reader);
// 使用索引搜索对象,执行搜索,返回结果集TopDocs
// 第一个参数:搜索对象,第二个参数:返回的数据条数,指定查询结果最顶部的n条数据返回
TopDocs topDocs = searcher.search(query, 10);
System.out.println("查询到的数据总条数是:" + topDocs.totalHits);
// 获取查询结果集
ScoreDoc[] docs = topDocs.scoreDocs;
// 解析结果集
for (ScoreDoc scoreDoc : docs) {
// 获取文档id
int docID = scoreDoc.doc;
Document doc = searcher.doc(docID);
System.out.println("======================================");
System.out.println("docID:" + docID);
System.out.println("bookId:" + doc.get("id"));
System.out.println("name:" + doc.get("name"));
System.out.println("price:" + doc.get("price"));
System.out.println("pic:" + doc.get("pic"));
// System.out.println("desc:" + doc.get("desc"));
}
// 3. 释放资源
reader.close();
}
-
-
- NumericRangeQuery
-
NumericRangeQuery,指定数字范围查询.
@Test
public void testSearchNumericRangeQuery() throws Exception {
// 创建NumericRangeQuery搜索对象,数字范围查询.
// 五个参数分别是:域名、最小值、最大值、是否包含最小值,是否包含最大值
Query query = NumericRangeQuery.newFloatRange("price", 54f, 56f, false, true);
doSearch(query);
}
-
-
- BooleanQuery
-
BooleanQuery,布尔查询,实现组合条件查询。
@Test
public void testSearchBooleanQuery() throws Exception {
// 创建TermQuery搜索对象
Query query1 = new TermQuery(new Term("name", "lucene"));
// 创建NumericRangeQuery搜索对象,数字范围查询.
// 四个参数分别是:域名、最小值、最大值、是否包含最小值,是否包含最大值
Query query2 = NumericRangeQuery.newFloatRange("price", 54f, 66f, false, true);
// 创建BooleanQuery搜索对象,组合查询条件
BooleanQuery boolQuery = new BooleanQuery();
// 组合条件,
// 第一个参数,查询条件,第二个参数,组合方式
boolQuery.add(query1, Occur.MUST_NOT);
boolQuery.add(query2, Occur.MUST);
doSearch(boolQuery);
}
组合关系代表的意思如下:
1、MUST和MUST表示“与”的关系,即“交集”。
2、MUST和MUST_NOT前者包含后者不包含。
3、MUST_NOT和MUST_NOT没意义
4、SHOULD与MUST表示MUST,SHOULD失去意义;
5、SHOULD与MUST_NOT相当于MUST与MUST_NOT。
6、SHOULD与SHOULD表示“或”的关系,即“并集”。
-
- 通过QueryParser搜索
通过QueryParser也可以创建Query,QueryParser提供一个Parse方法,此方法可以直接根据查询语法来查询。可以通过打印Query对象的方式,查看生成的查询语句。
-
-
- 查询语法
-
1、基础的查询语法,关键词查询:
域名+“:”+搜索的关键字
例如:name:java
- 范围查询
域名+“:”+[最小值 TO 最大值]
例如:size:[1 TO 1000]
注意:QueryParser不支持对数字范围的搜索,它支持字符串范围。数字范围搜索建议使用NumericRangeQuery。
- 组合条件查询
Occur.MUST 查询条件必须满足,相当于AND | +(加号) |
Occur.SHOULD 查询条件可选,相当于OR
| 空(不用符号) |
Occur.MUST_NOT 查询条件不能满足,相当于NOT非 | -(减号) |
-
-
- QueryParser
-
@Test
public void testSearchIndex() throws Exception {
// 创建分词器
Analyzer analyzer = new StandardAnalyzer();
// 1. 创建Query搜索对象
// 创建搜索解析器,第一个参数:默认Field域,第二个参数:分词器
QueryParser queryParser = new QueryParser("desc", analyzer);
// 创建搜索对象
// Query query = queryParser.parse("desc:java学习");
Query query = queryParser.parse("desc:java AND lucene");
// 打印生成的搜索语句
System.out.println(query);
// 执行搜索
doSearch(query);
}
-
-
- MultiFieldQueryParser
-
通过MultiFieldQueryParse对多个域查询。
@Test
public void testSearchMultiFieldQueryParser() throws Exception {
// 创建分词器
Analyzer analyzer = new IKAnalyzer();
// 1. 创建MultiFieldQueryParser搜索对象
String[] fields = { "name", "desc" };
MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser(fields, analyzer);
// 创建搜索对象
Query query = multiFieldQueryParser.parse("lucene");
// 打印生成的搜索语句
System.out.println(query);
// 执行搜索
doSearch(query);
}
生成的查询语句:
name:lucene desc:lucene
-
- TopDocs
Lucene搜索结果可通过TopDocs遍历,TopDocs类提供了少量的属性,如下:
方法或属性 | 说明 |
totalHits | 匹配搜索条件的总记录数 |
scoreDocs | 顶部匹配记录 |
注意:
Search方法需要指定匹配记录数量n:indexSearcher.search(query, n)
TopDocs.totalHits:是匹配索引库中所有记录的数量
TopDocs.scoreDocs:匹配相关度高的前边记录数组,scoreDocs的长度小于等于search方法指定的参数n
- 相关度排序(了解)
- 什么是相关度排序
相关度排序是查询结果按照与查询关键字的相关性进行排序,越相关的越靠前。比如搜索“Lucene”关键字,与该关键字最相关的文章应该排在前边。
SEO 优化
设置Boost属性值
如果两个属性值一样那么后面写入的权重高
-
- 相关度打分
Lucene对查询关键字和索引文档的相关度进行打分,得分高的就排在前边。如何打分呢?Lucene是在用户进行检索时实时根据搜索的关键字计算出来的,分两步:
1)计算出词(Term)的权重
2)根据词的权重值,计算文档相关度得分。
什么是词的权重?
通过索引部分的学习,明确索引的最小单位是一个Term(索引词典中的一个词)。搜索也是从索引域中查询Term,再根据Term找到文档。Term对文档的重要性称为权重,影响Term权重有两个因素:
- Term Frequency (tf):
指此Term在此文档中出现了多少次。tf 越大说明越重要。
词(Term)在文档中出现的次数越多,说明此词(Term)对该文档越重要,如“Lucene”这个词,在文档中出现的次数很多,说明该文档主要就是讲Lucene技术的。
- Document Frequency (df):
指有多少文档包含此Term。df 越大说明越不重要。
比如,在一篇英语文档中,this出现的次数更多,就说明越重要吗?不是的,有越多的文档包含此词(Term), 说明此词(Term)太普通,不足以区分这些文档,因而重要性越低。
-
- 设置boost值影响相关度排序
boost是一个加权值(默认加权值为1.0f),它可以影响权重的计算。在索引时对某个文档中的field设置加权值,设置越高,在搜索时匹配到这个文档就可能排在前边。
未设置权重:
希望把name为spring的排名提高
正在上传…重新上传取消
先清空索引库,然后修改创建索引的代码,添加设置加权值的逻辑
修改创建索引代码:
@Test
public void testCreateIndex() throws Exception {
// 1. 采集数据
BookDao bookDao = new BookDaoImpl();
List<Book> bookList = bookDao.queryBookList();
// 2. 创建Document文档对象
List<Document> documents = new ArrayList<>();
for (Book book : bookList) {
Document document = new Document();
// Document文档中添加域
// 图书Id
// Store.YES:表示存储到文档域中
// 不分词,不索引,储存
document.add(new StoredField("id", book.getId().toString()));
// 图书名称
// 分词,索引,储存
document.add(new TextField("name", book.getName().toString(), Store.YES));
// 图书价格
// 分词,索引,储存
document.add(new FloatField("price", book.getPrice(), Store.YES));
// 图书图片地址
// 不分词,不索引,储存
document.add(new StoredField("pic", book.getPic().toString()));
// 图书描述
// 分词,索引,不储存
TextField descField = new TextField("desc", book.getDesc().toString(), Store.NO);
// 给id为4的文档设置加权值
if (4 == book.getId()) {
descField.setBoost(100f);
}
document.add(descField);
// 把Document放到list中
documents.add(document);
}
// 3. 创建Analyzer分词器,分析文档,对文档进行分词
Analyzer analyzer = new StandardAnalyzer();
// 4. 创建IndexWrite,需要directory流对象
// 创建流对象
Directory directory = FSDirectory.open(new File("D:/itcast/lucene/index"));
// 创建IndexWriteConfig对象
IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);
// 创建IndexWriter写入对象
IndexWriter indexWriter = new IndexWriter(directory, config);
// 通过IndexWriter添加文档对象document
for (Document doc : documents) {
indexWriter.addDocument(doc);
}
// 释放资源
indexWriter.close();
}
执行创建索引的逻辑,使用luke重载新生成的索引库,再次查询spring在第一
查询结果:
正在上传…重新上传取消
- Solr介绍
- 什么是solr
Solr 是Apache下的一个顶级开源项目,采用Java开发,它是基于Lucene的全文搜索服务。Solr可以独立运行在Jetty、Tomcat等这些Servlet容器中。
使用Solr 进行创建索引和搜索索引的实现方法很简单,如下:
- 创建索引:客户端(可以是浏览器可以是Java程序)用 POST 方法向 Solr 服务器发送一个描述 Field 及其内容的 XML 文档,Solr服务器根据xml文档添加、删除、更新索引 。
- 搜索索引:客户端(可以是浏览器可以是Java程序)用 GET方法向 Solr 服务器发送请求,然后对 Solr服务器返回Xml、json等格式的查询结果进行解析。Solr不提供构建页面UI的功能。Solr提供了一个管理界面,通过管理界面可以查询Solr的配置和运行情况。
Solr是一个可以独立运行的搜索服务器,使用solr进行全文检索服务的话,只需要通过http请求访问该服务器即可。
-
- Solr和Lucene的区别
Lucene是一个开放源代码的全文检索引擎工具包,它不是一个完整的全文检索应用。Lucene仅提供了完整的查询引擎和索引引擎,目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者以Lucene为基础构建全文检索应用。
Solr的目标是打造一款企业级的搜索引擎系统,它是基于Lucene一个搜索引擎服务,可以独立运行,通过Solr可以非常快速的构建企业的搜索引擎,通过Solr也可以高效的完成站内搜索功能。
正在上传…重新上传取消
- Solr安装配置
- 下载solr
Solr和lucene的版本是同步更新的,本课程使用的版本:4.10.3
下载地址:http://archive.apache.org/dist/lucene/solr/
Linux下需要solr-4.10.3.tgz,windows下需要solr-4.10.3.zip。
正在上传…重新上传取消
解压solr-4.10.3.zip:
正在上传…重新上传取消
bin:solr的运行脚本
contrib:solr的一些扩展jar包,用于增强solr的功能。
dist:该目录包含build过程中产生的war和jar文件,以及相关的依赖文件。
docs:solr的API文档
example:solr工程的例子目录:
- example/solr:
该目录是一个标准的SolrHome,它包含一个默认的SolrCore
- example/multicore:
该目录包含了在Solr的multicore中设置的多个Core目录。
- example/webapps:
该目录中包括一个solr.war,该war可作为solr的运行实例工程。
licenses:solr相关的一些许可信息
-
- 运行环境
solr 需要运行在一个Servlet容器中,Solr4.10.3要求jdk使用1.7以上,Solr默认提供Jetty(java写的Servlet容器),
使用jetty启动:
使用cmd命令行,进入example文件夹启动
启动命令java -jar start.jar
正在上传…重新上传取消
启动后访问地址:http://127.0.0.1:8983/solr
但是企业中一般使用Tomcat作为服务器,本课程也是一样,
相关环境如下:
- Solr:4.10.3
- Jdk环境:1.7(solr4.10 不能使用jdk1.7以下)
- 服务器:Tomcat 7
-
- SolrCore配置(了解)
- SolrHome和SolrCore
- SolrCore配置(了解)
SolrHome是Solr服务运行的主目录,该目录中包括了多个SolrCore目录。SolrCore目录中包含了运行Solr实例所有的配置文件和数据文件,Solr实例就是SolrCore。
每个SolrCore提供单独的搜索和索引服务。
-
-
-
- 目录结构
-
-
SolrHome目录:
正在上传…重新上传取消
SolrCore目录:
正在上传…重新上传取消
-
-
- 创建SolrCore
-
创建SolrCore先要创建SolrHome。在solr解压包下solr-4.10.3\example\solr文件夹就是一个标准的SolrHome,只需要将它复制到指定的目录下即可。
拷贝solr解压包下solr-4.10.3\example\solr文件夹。
正在上传…重新上传取消
复制该文件夹到本地的一个目录,把文件名称改为solrhome。
改名不是必须的,只是为了便于理解
正在上传…重新上传取消
打开solrhome目录确认solrcore
正在上传…重新上传取消
-
-
- 配置SolrCore
-
其实就是配置SolrCore目录下的conf/solrconfig.xml。
正在上传…重新上传取消
这个文件是来配置SolrCore实例的相关信息。如果使用默认配置可以不用做任何修改。它里面包含了不少标签,但是我们经常使用的标签为:lib标签、datadir标签、requestHandler标签。
-
-
-
- lib 标签
-
-
在solrconfig.xml中可以加扩展载一些的jar,如果需要使用,则首先要把这些jar复制到指定的目录,我们复制到SolrHome同级目录
复制之前解压的文件夹中的contrib和dist文件夹
正在上传…重新上传取消
粘贴到SolrHome同级目录下。
正在上传…重新上传取消
修改solrconfig.xml配置文件加载扩展的jar。
configsolr.install.dir表示${SolrCore}的目录位置,需要如下修改:
./ 表示当前目录 ../表示上一级目录
正在上传…重新上传取消
-
-
-
- datadir标签
-
-
配置SolrCore的data目录。
data目录用来存放SolrCore的索引文件和tlog日志文件
solr.data.dir表示${SolrCore}/data的目录位置
正在上传…重新上传取消
如果不想使用默认的目录也可以通过solrconfig.xml更改索引目录 ,
例如:
正在上传…重新上传取消
(建议不修改,否则配置多个SolrCore会报错)
-
-
-
- requestHandler标签
-
-
requestHandler请求处理器,定义了索引和搜索的访问方式。
通过/update维护索引,可以完成索引的添加、修改、删除操作。
正在上传…重新上传取消
通过/select搜索索引。
正在上传…重新上传取消
设置搜索参数完成搜索,搜索参数也可以设置一些默认值,如下:
<requestHandler name="/select" class="solr.SearchHandler">
<!-- 设置默认的参数值,可以在请求地址中修改这些参数-->
<lst name="defaults">
<str name="echoParams">explicit</str>
<int name="rows">10</int><!--显示数量-->
<str name="wt">json</str><!--显示格式-->
<str name="df">text</str><!--默认搜索字段-->
</lst>
</requestHandler>
-
- Solr工程部署
由于在项目中用到的web服务器大多数是用的Tomcat,所以就进行solr和Tomcat的整合。
-
-
- 安装Tomcat
-
复制自己的Tomcat7到这里
正在上传…重新上传取消
删除不用的应用(可以不删)
正在上传…重新上传取消
修改server.xml配置文件里面的端口号(否则后面eclipse使用Tomcat会冲突)
正在上传…重新上传取消
修改以下三个端口号
正在上传…重新上传取消
正在上传…重新上传取消
正在上传…重新上传取消
-
-
- 部署solr.war到Tomcat中
-
- 从solr解压包下的solr-4.10.3\example\webapps目录中拷贝solr.war
复制solr.war
正在上传…重新上传取消
粘贴到自己Tomcat的webapps里
正在上传…重新上传取消
在Tomcat的webapps里,把war解压到当前路径,并删除solr.war
正在上传…重新上传取消
效果:
正在上传…重新上传取消
正在上传…重新上传取消
-
-
- 添加solr服务的扩展jar包(日志包)
-
把solr解压包下solr-4.10.3\example\lib\ext目录下的所有jar包拷贝到Tomcat部署的solr的WEB-INF/lib文件夹
复制扩展jar包
正在上传…重新上传取消
粘贴到Tomcat的webapps的solr工程的WEB-INF\lib目录
正在上传…重新上传取消
-
-
- 配置solr应用的web.xml
-
需要修改web.xml,让Tomcat使用JNDI的方式告诉solr服务器SolrHome在哪。
正在上传…重新上传取消
修改内容:
第42行的Solr/home名称必须是固定的,修改第43行,如下图
正在上传…重新上传取消
-
-
- 启动Tomcat进行访问
-
访问
http://localhost:8081/solr/
出现以下界面则说明solr安装成功!!!
-
- 管理界面功能介绍正在上传…重新上传取消
- Dashboard
- 管理界面功能介绍正在上传…重新上传取消
仪表盘,显示了该Solr实例开始启动运行的时间、版本、系统资源、jvm等信息。
-
-
- Logging
-
Solr运行日志信息
-
-
- Cloud
-
Cloud即SolrCloud,即Solr云(集群),当使用Solr Cloud模式运行时会显示此菜单,该部分功能在第二个项目,即电商项目会演示。
-
-
- Core Admin
-
Solr Core的管理界面。在这里可以添加SolrCore实例(有bug,不推荐使用浏览器界面添加SolrCore)。
-
-
- java properties
-
Solr在JVM 运行环境中的属性信息,包括类路径、文件编码、jvm内存设置等信息。
-
-
- Tread Dump
-
显示Solr Server中当前活跃线程信息,同时也可以跟踪线程运行栈信息。
-
-
- Core selector
-
选择一个SolrCore进行详细操作,如下:
正在上传…重新上传取消
-
-
-
- Analysis
-
-
通过此界面可以测试索引分析器和搜索分析器的执行情况
正在上传…重新上传取消
-
-
-
- dataimport
-
-
可以定义数据导入处理器,从关系数据库将数据导入到Solr索引库中。
默认没有配置,需要手工配置。
-
-
-
- Document
-
-
通过/update表示更新索引,solr默认根据id(唯一约束)域来更新Document的内容,如果根据id值搜索不到id域则会执行添加操作,如果找到则更新。
通过此菜单可以创建索引、更新索引、删除索引等操作,界面如下:
正在上传…重新上传取消
- overwrite="true" : solr在做索引的时候,如果文档已经存在,就用xml中的文档进行替换
- commitWithin="1000" : solr 在做索引的时候,每隔1000(1秒)毫秒,做一次文档提交。为了方便测试也可以在Document中立即提交,</doc>后添加“<commit/>”
-
-
-
- Query
-
-
通过/select执行搜索索引,必须指定“q”查询条件方可搜索。
正在上传…重新上传取消
- Solrj的使用
- 什么是solrj
solrj是访问Solr服务的java客户端,提供索引和搜索的请求方法,如下图:
Index索引库 |
javaEE应用程序
SolrJ程序客户端
|
Solr服务
Tomcat |
通过SolrJ请求Solr服务 |
最终Solr完在索引和搜索 |
Solrj和图形界面操作的区别就类似于数据库中使用jdbc和mysql客户端的区别一样。
-
- 需求
使用solrj调用solr服务实现对索引库的增删改查操作。
-
- 环境准备
- Solr:4.10.3
- Jdk环境:1.7
- IDE环境:Eclipse Mars2
-
- 工程搭建
- 创建java工程
- 工程搭建
正在上传…重新上传取消
-
-
- 添加jar
-
Solrj的包,\solr-4.10.3\dist\目录下
正在上传…重新上传取消
solrj依赖包,\solr-4.10.3\dist\solrj-lib
正在上传…重新上传取消
Solr服务的依赖包,\solr\example\lib\ext
正在上传…重新上传取消
-
- 代码实现
- 添加&修改索引
- 步骤
- 添加&修改索引
- 代码实现
- 创建HttpSolrServer对象,通过它和Solr服务器建立连接。
- 创建SolrInputDocument对象,然后通过它来添加域。
- 通过HttpSolrServer对象将SolrInputDocument添加到索引库。
- 提交。
-
-
-
- 代码
-
-
说明:根据id(唯一约束)域来更新Document的内容,如果根据id值搜索不到id域则会执行添加操作,如果找到则更新。
@Test
public void testCreateAndUpdateIndex() throws Exception {
// 1. 创建HttpSolrServer对象
// 设置solr服务接口,浏览器客户端地址http://127.0.0.1:8081/solr/#/
String baseURL = "http://127.0.0.1:8081/solr";
HttpSolrServer httpSolrServer = new HttpSolrServer(baseURL);
// 2. 创建SolrInputDocument对象
SolrInputDocument document = new SolrInputDocument();
document.addField("id", "c1001");
document.addField("content ", "Hello world!");
// 3. 把SolrInputDocument对象添加到索引库中
httpSolrServer.add(document);
// 4. 提交
httpSolrServer.commit();
}
-
-
- 删除索引
- 代码
- 删除索引
-
抽取HttpSolrServer 的创建代码
private HttpSolrServer httpSolrServer;
// 提取HttpSolrServer创建
@Before
public void init() {
// 1. 创建HttpSolrServer对象
// 设置solr服务接口,浏览器客户端地址http://127.0.0.1:8081/solr/#/
String baseURL = "http://127.0.0.1:8081/solr/";
this.httpSolrServer = new HttpSolrServer(baseURL);
}
删除索引逻辑,两种:
根据id删除
根据条件删除,根据条件删除
可以使用*:*作为条件,就是删除所有数据(慎用)
@Test
public void testDeleteIndex() throws Exception {
// 根据id删除索引数据
// this.httpSolrServer.deleteById("c1001");
// 根据条件删除(如果是*:*就表示全部删除,慎用)
this.httpSolrServer.deleteByQuery("*:*");
// 提交
this.httpSolrServer.commit();
}
-
-
- 查询索引
- 简单查询
- 查询索引
-
/**
* 简单搜索
*
* @throws Exception
*/
@Test
public void testSearchIndex1() throws Exception {
// 创建搜索对象
SolrQuery query = new SolrQuery();
// 设置搜索条件
query.setQuery("*:*");
// 发起搜索请求
QueryResponse response = this.httpSolrServer.query(query);
// 处理搜索结果
SolrDocumentList results = response.getResults();
System.out.println("搜索到的结果总数:" + results.getNumFound());
// 遍历搜索结果
for (SolrDocument solrDocument : results) {
System.out.println("----------------------------------------------------");
System.out.println("id:" + solrDocument.get("id"));
System.out.println("content" + solrDocument.get("content"));
}
- solr基本使用
- schema.xml
schema.xml文件在SolrCore的conf目录下,在此配置文件中定义了域以及域的类型等一些配置。在solr中域必须先定义后使用。
正在上传…重新上传取消
-
-
- field
-
<field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" />
- Name:域的名称
- Type:域的类型
- Indexed:是否索引
- Stored:是否存储
- Required:是否必须
- multiValued:是否是多值,存储多个值时设置为true,solr允许一个Field存储多个值,比如存储一个用户的好友id(多个),商品的图片(多个,大图和小图)
-
- dynamicField(动态域)
-
<dynamicField name="*_s" type="string" indexed="true" stored="true" />
Name:动态域的名称,是一个表达式,*匹配任意字符,只要域的名称和表达式的规则能够匹配就可以使用。
例如:搜索时查询条件[product_i:钻石]就可以匹配这个动态域,可以直接使用,不用单独再定义一个product_i域。
-
-
- uniqueKey
-
<uniqueKey>id</uniqueKey>
相当于主键,每个文档中必须有一个id域。
-
-
- copyField(复制域)
-
<copyField source="cat" dest="text"/>
可以将多个Field复制到一个Field中,以便进行统一的检索。当创建索引时,solr服务器会自动的将源域的内容复制到目标域中。
- source:源域
- dest:目标域,搜索时,指定目标域为默认搜索域,可以提高查询效率。
定义目标域:
<field name="text" type="text_general" indexed="true" stored="false" multiValued="true"/>
目标域必须要使用:multiValued="true"
-
-
- fieldType(域类型)
-
<fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
- name:域类型的名称
- class:指定域类型的solr类型。
- analyzer:指定分词器。在FieldType定义的时候最重要的就是定义这个类型的数据在建立索引和进行查询的时候要使用的分析器analyzer,包括分词和过滤。
- type:index和query。Index 是创建索引,query是查询索引。
- tokenizer:指定分词器
- filter:指定过滤器
-
- 配置中文分析器
使用IKAnalyzer中文分析器
正在上传…重新上传取消
第一步:把IKAnalyzer2012FF_u1.jar添加到solr/WEB-INF/lib目录下。
正在上传…重新上传取消
第二步:复制IKAnalyzer的配置文件和自定义词典和停用词词典到solr的solr/WEB-INF/classes目录下。
复制IK分词器配置文件、自定义词典、停用词词典
正在上传…重新上传取消
粘贴到Tomcat的solr的/WEB-INF/classes目录下
正在上传…重新上传取消
第三步:在schema.xml中添加一个自定义的fieldType,使用中文分析器。
<!-- IKAnalyzer-->
<fieldType name="text_ik" class="solr.TextField">
<analyzer class="org.wltea.analyzer.lucene.IKAnalyzer"/>
</fieldType>
第四步:在schema.xml中添加field,指定field的type属性为text_ik
<!--IKAnalyzer Field-->
<field name="content_ik" type="text_ik" indexed="true" stored="true" />
第五步:重启tomcat
效果:
正在上传…重新上传取消
-
- 配置业务Field
- 需求
- 配置业务Field
要使用solr实现网站中商品搜索,需要 将mysql数据库中数据在solr中创建索引。
- 需要在solr的schema.xml文件定义要存储的商品Field。
- 需要把MySQL的数据导入到solr索引库中
- 开发搜索功能
-
- 数据库添加数据
-
在数据库中运行solr.sql脚本
正在上传…重新上传取消
-
-
- 定义Field
-
先确定定义的商品document的Field域有哪些?
可以根据mysql数据库中商品表的字段来确定:
products商品表:
正在上传…重新上传取消
Schema.xml中配置业务域
<!--product-->
<field name="product_name" type="text_ik" indexed="true" stored="true"/>
<field name="product_price" type="float" indexed="true" stored="true"/>
<field name="product_description" type="text_ik" indexed="true" stored="false" />
<field name="product_picture" type="string" indexed="false" stored="true" />
<field name="product_catalog_name" type="string" indexed="true" stored="true" />
<field name="product_keywords" type="text_ik" indexed="true" stored="false" multiValued="true"/>
<copyField source="product_name" dest="product_keywords"/>
<copyField source="product_description" dest="product_keywords"/>
-
- dataimportHandler插件
使用dataimport插件批量导入数据。
第一步:把dataimport插件依赖的jar包添加到solrcore(collection1\lib)中, 还需要mysql的数据库驱动。
正在上传…重新上传取消
第二步:配置solrconfig.mxl文件,添加一个requestHandler。
<requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler"> <lst name="defaults"> <str name="config">data-config.xml</str> </lst> </requestHandler> |
第三步:创建一个data-config.xml,保存到collection1\conf\目录下
<?xml version="1.0" encoding="UTF-8" ?> <dataConfig> <dataSource type="JdbcDataSource" driver="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/lucene" user="root" password="root"/> <document> <entity name="product" query="SELECT pid,name,catalog_name,price,description,picture FROM products "> <field column="pid" name="id"/> <field column="name" name="product_name"/> <field column="catalog_name" name="product_catalog_name"/> <field column="price" name="product_price"/> <field column="description" name="product_description"/> <field column="picture" name="product_picture"/> </entity> </document>
</dataConfig> |
第四步:重启tomcat
正在上传…重新上传取消
第五步:点击“execute”按钮导入数据
注意:导入数据前会先清空索引库,然后再导入。
- solrj的复杂查询
- solr的查询语法
- q: 查询关键字,必须的。
请求的q是字符串,如果查询所有使用*:*
正在上传…重新上传取消
- fq: (filter query)过滤查询
作用:在q查询符合结果中同时是fq查询符合的
请求fq是一个数组(多个值)
过滤查询价格从1到20的记录。
正在上传…重新上传取消
也可以使用“*”表示无限,例如:
20以上:product_price:[20 TO *]
20以下:product_price:[* TO 20]
也可以在“q”查询条件中使用product_price:[1 TO 20],
如下效果和上面一样:
正在上传…重新上传取消
- sort: 排序,desc代表降序,asc代表升序
按照价格升序排
正在上传…重新上传取消
- start: 分页显示使用,开始记录下标,从0开始
rows: 指定返回结果最多有多少条记录,配合start来实现分页。
正在上传…重新上传取消
- fl: (Field List)指定返回那些字段内容,用逗号或空格分隔多个。
正在上传…重新上传取消
显示商品id、商品名称、商品分类名称
- df: 指定默认搜索Field
正在上传…重新上传取消
- wt: (writer type)指定输出格式,可以有 xml, json, php, phps
正在上传…重新上传取消
- hl: 是否高亮 ,设置高亮Field,设置格式前缀和后缀。
正在上传…重新上传取消
-
- solrj的复杂查询
页面的查询条件,复杂查询条件和页面的查询条件一致
正在上传…重新上传取消
代码实现:
//复杂查询索引 @Test public void queryIndex2() throws Exception { //创建连接 SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr"); //创建一个query对象 SolrQuery query = new SolrQuery(); //设置查询条件 query.setQuery("钻石"); //过滤条件 query.setFilterQueries("product_catalog_name:幽默杂货"); //排序条件 query.setSort("product_price", ORDER.asc); //分页处理 query.setStart(0); query.setRows(10); //结果中域的列表 query.setFields("id","product_name","product_price","product_catalog_name","product_picture"); //设置默认搜索域 query.set("df", "product_keywords"); //高亮显示 query.setHighlight(true); //高亮显示的域 query.addHighlightField("product_name"); //高亮显示的前缀 query.setHighlightSimplePre("<em>"); //高亮显示的后缀 query.setHighlightSimplePost("</em>"); //执行查询 QueryResponse queryResponse = solrServer.query(query); //取查询结果 SolrDocumentList solrDocumentList = queryResponse.getResults(); //共查询到商品数量 System.out.println("共查询到商品数量:" + solrDocumentList.getNumFound()); //遍历查询的结果 for (SolrDocument solrDocument : solrDocumentList) { System.out.println(solrDocument.get("id")); //取高亮显示 String productName = ""; Map<String, Map<String, List<String>>> highlighting = queryResponse.getHighlighting(); List<String> list = highlighting.get(solrDocument.get("id")).get("product_name"); //判断是否有高亮内容 if (null != list) { productName = list.get(0); } else { productName = (String) solrDocument.get("product_name"); }
System.out.println(productName); System.out.println(solrDocument.get("product_price")); System.out.println(solrDocument.get("product_catalog_name")); System.out.println(solrDocument.get("product_picture"));
} } |
- 案例
- 需求
使用Solr实现电商网站中商品信息搜索功能,可以根据关键字搜索商品信息,根据商品分类、价格过滤搜索结果,也可以根据价格进行排序,实现分页。
界面如下:
正在上传…重新上传取消
-
-
- 架构分析
-
spring容器 |
表现层 springmvc |
Service层 |
Dao层 |
Solr索引库 |
mysql数据库 |
Tomcat
|
Solr服务 |
索引、搜索请求 |
架构分为:
-
- solr服务器
- 自己的web服务器(需要开发)
- 数据库mysql
自己开发的应用
-
- Controller
获取搜索条件,并响应搜索结果到前台页面。
-
- Service
使用solrj来调用solr的服务进行索引和搜索
Service调用dao进行商品数据的维护时,要同步更新索引库(不实现)
-
- Dao(本案例不实现)
对商品数据进行维护和查询
-
- 环境准备
- Solr:4.10.3
- Jdk环境:1.7
- IDE环境:eclipse Mars2
- 服务器:Tomcat 7
- 工程搭建
创建一个web工程导入jar包
- springmvc的相关jar包
- solrJ的jar包
- Example\lib\ext下的jar包
正在上传…重新上传取消
-
- Dao
功能:接收service层传递过来的参数,根据参数查询索引库,返回查询结果。
参数:SolrQuery对象
返回值:一个商品列表List<ProductModel>,还需要返回查询结果的总数量。
返回:ResultModel
方法定义:ResultModel queryProduct(SolrQuery query) throws Exception;
商品对象模型:
public class ProductModel {
// 商品编号
private String pid;
// 商品名称
private String name;
// 商品分类名称
private String catalog_name;
// 价格
private float price;
// 商品描述
private String description;
// 图片名称
private String picture;
}
返回值对象模型
public class ResultModel {
// 商品列表
private List<ProductModel> productList;
// 商品总数
private Long recordCount;
// 总页数
private int pageCount;
// 当前页
private int curPage;
}
@Repository public class ProductDaoImpl implements ProductDao {
@Autowired private SolrServer solrServer;
@Override public ResultModel queryProduct(SolrQuery query) throws Exception {
ResultModel resultModel = new ResultModel(); //根据query对象查询商品列表 QueryResponse queryResponse = solrServer.query(query); SolrDocumentList solrDocumentList = queryResponse.getResults(); //取查询结果的总数量 resultModel.setRecordCount(solrDocumentList.getNumFound()); List<ProductModel> productList = new ArrayList<>(); //遍历查询结果 for (SolrDocument solrDocument : solrDocumentList) { //取商品信息 ProductModel productModel = new ProductModel(); productModel.setPid((String) solrDocument.get("id")); //取高亮显示 String productName = ""; Map<String, Map<String, List<String>>> highlighting = queryResponse.getHighlighting(); List<String> list = highlighting.get(solrDocument.get("id")).get("product_name"); if (null != list) { productName = list.get(0); } else { productName = (String) solrDocument.get("product_name"); } productModel.setName(productName); productModel.setPrice((float) solrDocument.get("product_price")); productModel.setCatalog_name((String) solrDocument.get("product_catalog_name")); productModel.setPicture((String) solrDocument.get("product_picture")); //添加到商品列表 productList.add(productModel); } //商品列表添加到resultmodel中 resultModel.setProductList(productList); return resultModel; }
} |
-
- Service
功能:接收action传递过来的参数,根据参数拼装一个查询条件,调用dao层方法,查询商品列表。接收返回的商品列表和商品的总数量,根据每页显示的商品数量计算总页数。
参数:
1、查询条件:字符串
2、商品分类的过滤条件:商品的分类名称,字符串
3、商品价格区间:传递一个字符串,满足格式:“0-100、101-200、201-*”
4、排序条件:页面传递过来一个升序或者降序就可以,默认是价格排序。0:升序1:降序
5、分页信息:每页显示的记录条数创建一个常量60条。传递一个当前页码就可以了。
返回值:ResultModel
方法定义:ResultModel queryProduct(String queryString, String caltalog_name, String price, String sort, Integer page) throws Exception;
@Service public class ProductServiceImpl implements ProductService {
@Autowired private ProductDao productDao;
@Override public ResultModel queryProduct(String queryString, String caltalog_name, String price, String sort, Integer page) throws Exception { //拼装查询条件 SolrQuery query = new SolrQuery(); //查询条件 if (null != queryString && !"".equals(queryString)) { query.setQuery(queryString); } else { query.setQuery("*:*"); } //商品分类名称过滤 if (null != caltalog_name && !"".equals(caltalog_name)) { query.addFilterQuery("product_catalog_name:" + caltalog_name); } //价格区间过滤 if (null != price && !"".equals(price)) { String[] strings = price.split("-"); query.addFilterQuery("product_price:["+strings[0]+" TO "+strings[1]+"]"); } //排序条件 if ("1".equals(sort)) { query.setSort("product_price", ORDER.desc); } else { query.setSort("product_price", ORDER.asc); } //分页处理 if (null == page) { page = 1; } //start int start = (page-1) * Commons.PAGE_SIZE; query.setStart(start); query.setRows(Commons.PAGE_SIZE); //设置默认搜索域 query.set("df", "product_keywords"); //高亮设置 query.setHighlight(true); query.addHighlightField("product_name"); query.setHighlightSimplePre("<span style=\"color:red\">"); query.setHighlightSimplePost("</span>");
//查询商品列表 ResultModel resultModel = productDao.queryProduct(query); //计算总页数 long recordCount = resultModel.getRecordCount(); int pages = (int) (recordCount/Commons.PAGE_SIZE); if (recordCount % Commons.PAGE_SIZE > 0) { pages ++; } resultModel.setPageCount(pages); resultModel.setCurPage(page);
return resultModel; }
} |
-
- controller
功能:接收页面传递过来的参数调用service查询商品列表。将查询结果返回给jsp页面,还需要查询参数的回显。
参数:
1、查询条件:字符串
2、商品分类的过滤条件:商品的分类名称,字符串
3、商品价格区间:传递一个字符串,满足格式:“0-100、101-200、201-*”
4、排序条件:页面传递过来一个升序或者降序就可以,默认是价格排序。0:升序1:降序
5、分页信息:每页显示的记录条数创建一个常量60条。传递一个当前页码就可以了。
6、Model:相当于request。
返回结果:String类型,就是一个jsp的名称。
String queryProduct(String queryString, String caltalog_name, String price, String sort, Integer page, Model model) throws Exception;
@Controller public class ProductController {
@Autowired private ProductService productService;
@RequestMapping("/list") public String queryProduct(String queryString, String catalog_name, String price, String sort, Integer page, Model model) throws Exception { //查询商品列表 ResultModel resultModel = productService.queryProduct(queryString, catalog_name, price, sort, page); //列表传递给jsp model.addAttribute("result", resultModel); //参数回显 model.addAttribute("queryString", queryString); model.addAttribute("caltalog_name", catalog_name); model.addAttribute("price", price); model.addAttribute("sort", sort); model.addAttribute("page", page);
return "product_list"; } } |
-
- Jsp
参考资料。