Web-第二十八天 Lucene&solr使用一【悟空教程】

Web-第二十八天 Lucene&solr使用一【悟空教程】

Lucene/Solr

1. 搜索介绍

1.1. 实现搜索的方案

原来的方式实现搜索功能,我们的搜索流程如下图:

上图就是原始搜索引擎技术,如果用户比较少而且数据库的数据量比较小,那么这种方式实现搜索功能在企业中是比较常见的。

但是数据量过多时,数据库的压力就会变得很大,查询速度会变得非常慢。我们需要使用更好的解决方案来分担数据库的压力。

现在的方案(使用Lucene),如下图

为了解决数据库压力和速度的问题,我们的数据库就变成了索引库,我们使用Lucene的API的来操作服务器上的索引库。这样完全和数据库进行了隔离。

1.2. 数据查询方法

1.2.1. 顺序扫描法

所谓顺序扫描,例如要找内容包含一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫描完所有的文件。

这种方法是顺序扫描方法,数据量大就搜索慢。

1.2.2. 倒排索引

先举一个栗子:

例如我们使用新华字典查询汉字,新华字典有偏旁部首的目录(索引),我们查字首先查这个目录,找到这个目录中对应的偏旁部首,就可以通过这个目录中的偏旁部首找到这个字所在的位置(文档)。

现在有两篇文档:

Doc1: When in Rome, do as the Romans do.

Doc2: When do you come back from Rome?

Lucene会对以上两篇文档建立倒排索引

索引结构如下图:

1.提取资源中关键信息, 建立索引 (目录)

2.搜索时,根据关键字(目录),找到资源的位置

1.1. 搜索技术应用场景

应用场景 :

1.单机软件的搜索(word中的搜索)

2.站内搜索 (baidu贴吧、论坛、 京东、 taobao)

3.垂直领域的搜索 (818工作网)

4.专业搜索引擎公司 (google、baidu)

2. Lucene介绍

2.1. 什么是全文索引

计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式

2.2. 什么是Lucene

Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。

Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。

目前已经有很多应用程序的搜索功能是基于 Lucene 的,比如 Eclipse 的帮助系统的搜索功能。Lucene 能够为文本类型的数据建立索引,所以你只要能把你要索引的数据格式转化的文本的,Lucene 就能对你的文档进行索引和搜索。比如你要对一些 HTML 文档,PDF 文档进行索引的话你就首先需要把 HTML 文档和 PDF 文档转化成文本格式的,然后将转化后的内容交给 Lucene 进行索引,然后把创建好的索引文件保存到磁盘或者内存中,最后根据用户输入的查询条件在索引文件上进行查询。不指定要索引的文档的格式也使 Lucene 能够几乎适用于所有的搜索应用程序。

  • Lucene是一套用于全文检索和搜寻的开源程式库,由Apache软件基金会支 持和提供
  • Lucene提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻, 在Java开发环境里Lucene是一个成熟的免费开放源代码工具
  • Lucene并不是现成的搜索引擎产品,但可以用来制作搜索引擎产品

2.3. Lucene与搜索引擎的区别

全文检索系统是按照全文检索理论建立起来的用于提供全文检索服务的软件系统,包括建立索引、处理查询返回结果集、增加索引、优化索引结构等功能。例如:百度搜索、eclipse帮助搜索、淘宝网商品搜索等。

搜索引擎是全文检索技术最主要的一个应用,例如百度。搜索引擎起源于传统的信息全文检索理论,即计算机程序通过扫描每一篇文章中的每一个词,建立以词为单位的倒排文件,检索程序根据检索词在每一篇文章中出现的频率和每一个检索词在一篇文章中出现的概率,对包含这些检索词的文章进行排序,最后输出排序的结果。全文检索技术是搜索引擎的核心支撑技术。

Lucene和搜索引擎不同,Lucene是一套用java或其它语言写的全文检索的工具包,为应用程序提供了很多个api接口去调用,可以简单理解为是一套实现全文检索的类库,搜索引擎是一个全文检索系统,它是一个单独运行的软件系统

2.4. Lucene官网

官网: http://lucene.apache.org/

3. Lucene全文检索的流程

3.1. 索引和搜索流程图

1.绿色表示索引过程,对要搜索的原始内容进行索引构建一个索引库,索引过程包括:

确定原始内容即要搜索的内容à获得文档à创建文档à分析文档à索引文档

2.红色表示搜索过程,从索引库中搜索内容,搜索过程包括:

用户通过搜索界面à创建查询à执行搜索,从索引库搜索à渲染搜索结果

3.2. 索引流程

对文档索引的过程,将用户要搜索的文档内容进行索引,索引存储在索引库(index)中。

3.2.1. 原始内容

原始内容是指要索引和搜索的内容。

原始内容包括互联网上的网页、数据库中的数据、磁盘上的文件等。

3.2.2. 获得文档(采集数据)

从互联网上、数据库、文件系统中等获取需要搜索的原始信息,这个过程就是信息采集,采集数据的目的是为了对原始内容进行索引。

采集数据分类:

1.对于互联网上网页,可以使用工具将网页抓取到本地生成html文件。

2.数据库中的数据,可以直接连接数据库读取表中的数据。

3.文件系统中的某个文件,可以通过I/O操作读取文件的内容。

在Internet上采集信息的软件通常称为爬虫或蜘蛛,也称为网络机器人,爬虫访问互联网上的每一个网页,将获取到的网页内容存储起来。

Lucene不提供信息采集的类库,需要自己编写一个爬虫程序实现信息采集,也可以通过一些开源软件实现信息采集,如下:

Solr(http://lucene.apache.org/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的操作方法来取出和操作数据。

3.2.3. 创建文档

获取原始内容的目的是为了索引,在索引前需要将原始内容创建成文档(Document),文档中包括一个一个的域(Field),域中存储内容。

这里我们可以将磁盘上的一个文件当成一个document,Document中包括一些Field,如下图:

注意:每个Document可以有多个Field,不同的Document可以有不同的Field

3.2.4. 分析文档

将原始内容创建为包含域(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。。。。

3.2.5. 索引文档

对所有文档分析得出的语汇单元进行索引,索引的目的是为了搜索,最终要实现只搜索被索引的语汇单元从而找到Document(文档)。

创建索引是对语汇单元索引,通过词语找文档,这种索引的结构叫倒排索引结构。

倒排索引结构是根据内容(词汇)找文档,如下图:

倒排索引结构也叫反向索引结构,包括索引和文档两部分,索引即词汇表,它的规模较小,而文档集合较大。

3.3. 搜索流程

搜索就是用户输入关键字,从索引中进行搜索的过程。根据关键字搜索索引,根据索引找到对应的文档,从而找到要搜索的内容。

3.3.1. 用户

就是使用搜索的角色,用户可以是自然人,也可以是远程调用的程序。

3.3.2. 用户搜索界面

全文检索系统提供用户搜索的界面供用户提交搜索的关键字,搜索完成展示搜索结果。如下图:

Lucene不提供制作用户搜索界面的功能,需要根据自己的需求开发搜索界面。

3.3.3. 创建查询

用户输入查询关键字执行搜索之前需要先构建一个查询对象,查询对象中可以指定查询要查询关键字、要搜索的Field文档域等,查询对象会生成具体的查询语法,比如:

name:lucene表示要搜索name这个Field域中,内容为“lucene”的文档。

desc:lucene AND desc:java 表示要搜索既包括关键字“lucene”也包括“java”的文档。

3.3.4. 执行搜索

搜索索引过程:

1.根据查询语法在倒排索引词典表中分别找出对应搜索词的索引,从而找到索引所链接的文档链表。

例如搜索语法为“desc:lucene AND desc:java”表示搜索出的文档中既要包括lucene也要包括java。

2.由于是AND,所以要对包含lucene或java词语的链表进行交集,得到文档链表应该包括每一个搜索词语

3.获取文档中的Field域数据。

3.3.5. 渲染结果

以一个友好的界面将查询结果展示给用户,用户根据搜索结果找自己想要的信息,为了帮助用户很快找到自己的结果,提供了很多展示的效果,比如搜索结果中将关键字高亮显示,百度提供的快照等。

4. Lucene入门

4.1. Lucene准备

Lucene可以在官网上下载。课程已经准备好了Lucene的文件,我们使用的是4.10.3版本,文件位置如下图:

解压后的效果:

使用这三个文件的jar包,就可以实现lucene功能

本教程使用的数据是MySQL数据库的数据,所以还需要MySQL的连接包

学员编写的时候,也可以直接复制准备好的jar包,位置如下图:

4.2. 开发环境

JDK: 1.7 (Lucene4.8以上,必须使用JDK1.7及以上版本)

IDE: Eclipse Mars2

数据库: MySQL

数据库脚本位置如下图:

导入到MySQL效果如下图:

4.3. 创建Java工程

创建java工程测试即可,效果如下:

4.4. 索引流程

4.4.1. 数据采集

在电商网站中,全文检索的数据源在数据库中,需要通过jdbc访问数据库中book表的内容。

4.4.1.1. 创建pojo

public class Book {

// 图书ID

private Integer id;

// 图书名称

private String name;

// 图书价格

private Float price;

// 图书图片

private String pic;

// 图书描述

private String desc;

get/set。。。

}

4.4.1.2. 创建DAO接口

public interface BookDao {

/**

* 查询所有的book数据

*

* @return

*/

List<Book> queryBookList();

}

4.4.1.3. 创建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;

}

}

4.4.2. 实现索引流程

1. 采集数据

2. 创建Document文档对象

3. 创建分析器(分词器)

4. 创建Directory对象,声明索引库存储位置

5. 创建IndexWriterConfig配置信息类

6. 创建IndexWriter写入对象

7. 把Document写入到索引库中

8. 释放资源

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:/javahelp/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();

}

}

执行效果:

在文件夹中出现了以下文件,表示创建索引成功

4.4.3. 使用Luke查看索引

Luke作为Lucene工具包中的一个工具(http://www.getopt.org/luke/),可以通过界面来进行索引文件的查询、修改

luke所在位置如下图:

打开Luke方法:打开cmd命令行运行命令:java -jar lukeall-4.10.3.jar

打开后,使用如下图:

下图是索引域的展示效果:

下图是文档域展示效果

4.5. 搜索流程

4.5.1. 输入查询语句

Lucene可以通过query对象输入查询语句。同数据库的sql一样,lucene也有固定的查询语法:

最基本的有比如:AND, OR, NOT 等(必须大写)

举个栗子:

用户想找一个desc中包括java关键字和lucene关键字的文档。

它对应的查询语句:desc:java AND desc:lucene

如下图是使用luke搜索的例子:

4.5.1.1. 搜索分词

和索引过程的分词一样,这里要对用户输入的关键字进行分词,一般情况索引和搜索使用的分词器一致。

比如:输入搜索关键字“java学习”,分词后为java和学习两个词,与java和学习有关的内容都搜索出来了,如下:

4.5.2. 代码实现

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:/javahelp/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();

}

}

5. 分词器

5.1. 分词理解

在对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。

比如下边的文档经过分析器分析如下:

  • 原文档内容:
  • 分析后得到的多个语汇单元:

5.2. Analyzer使用时机

5.2.1. 索引时使用Analyzer

输入关键字进行搜索,当需要让该关键字与文档域内容所包含的词进行匹配时需要对文档域内容进行分析,需要经过Analyzer分析器处理生成语汇单元(Token)。分析器分析的对象是文档中的Field域。当Field的属性tokenized(是否分词)为true时会对Field值进行分析,如下图:

对于一些Field可以不用分析:

1.不作为查询条件的内容,比如文件路径

2.不是匹配内容中的词而匹配Field的整体内容,比如订单号、身份证号等。

5.2.2. 搜索时使用Analyzer

对搜索关键字进行分析和索引分析一样,使用Analyzer对搜索关键字进行分析、分词处理,使用分析后每个词语进行搜索。比如:搜索关键字:spring web ,经过分析器进行分词,得出:spring web拿词去索引词典表查找 ,找到索引链接到Document,解析Document内容。

对于匹配整体Field域的查询可以在搜索时不分析,比如根据订单号、身份证号查询等。

注意:搜索使用的分析器要和索引使用的分析器一致。

5.3. 中文分词器

5.3.1. 什么是中文分词器

学过英文的都知道,英文是以单词为单位的,单词与单词之间以空格或者逗号句号隔开。所以对于英文,我们可以简单以空格判断某个字符串是否为一个单词,比如I love China,love 和 China很容易被程序区分开来。

而中文则以字为单位,字又组成词,字和词再组成句子。中文“我爱中国”就不一样了,电脑不知道“中国”是一个词语还是“爱中”是一个词语。

把中文的句子切分成有意义的词,就是中文分词,也称切词。我爱中国,分词的结果是:我、爱、中国。

5.3.2. Lucene自带中文分词器

  • StandardAnalyzer:

单字分词:就是按照中文一个字一个字地进行分词。如:“我爱中国”, 效果:“我”、“爱”、“中”、“国”。

  • CJKAnalyzer

二分法分词:按两个字进行切分。如:“我是中国人”,效果:“我是”、“是中”、“中国”“国人”

上边两个分词器无法满足需求。

  • SmartChineseAnalyzer

对中文支持较好,但扩展性差,扩展词库,禁用词库和同义词库等不好处理

5.4. 第三方中文分词器

  • 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:最新版本在 https://github.com/NLPchina/ansj_seg 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算法。

5.5. 使用中文分词器IKAnalyzer

IKAnalyzer继承Lucene的Analyzer抽象类,使用IKAnalyzer和Lucene自带的分析器方法一样,将Analyzer测试代码改为IKAnalyzer测试中文分词效果。

如果使用中文分词器ik-analyzer,就需要在索引和搜索程序中使用一致的分词器:IK-analyzer。

5.5.1. 添加jar包

5.5.2. 修改分词器代码

@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:/javahelp/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();

}

5.6. 扩展中文词库

如果想配置扩展词和停用词,就创建扩展词的文件和停用词的文件。

注意:不要用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是存放停用词的地方

最终分词效果

6. Field域

6.1. Field属性

Field是文档中的域,包括Field名和Field值两部分,一个文档可以包括多个Field,Document只是Field的一个承载体,Field值即为要索引的内容,也是要搜索的内容。

  • 是否分词(tokenized)

是:作分词处理,即将Field值进行分词,分词的目的是为了索引。

比如:商品名称、商品描述等,这些内容用户要输入关键字搜索,由于搜索的内容格式大、内容多需要分词后将语汇单元建立索引

否:不作分词处理

比如:商品id、订单号、身份证号,图片url等

  • 是否索引(indexed)

是:进行索引。将Field分词后的词或整个Field值进行索引,存储到索引域,索引的目的是为了搜索。

比如:商品名称、商品描述分析后进行索引,订单号、身份证号不用分词但也要索引,这些将来都要作为查询条件。

否:不索引。

比如:图片路径、文件路径等,不用作为查询条件的不用索引。

  • 是否存储(stored)

是:将Field值存储在文档域中,存储在文档域中的Field才可以从Document中获取。

比如:商品名称、订单号,凡是将来要从Document中获取的Field都要存储。

否:不存储Field值

比如:商品描述,内容较大不用存储。如果要向用户展示商品描述可以从系统的关系数据库中获取。

6.2. 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)

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的策略.

6.3. Field修改

6.3.1. 修改分析

图书id:

是否分词:不用分词,因为不会根据商品id来搜索商品

是否索引:不索引,因为不需要根据图书ID进行搜索

是否存储:要存储,因为查询结果页面需要使用id这个值。

图书名称:

是否分词:要分词,因为要根据图书名称的关键词搜索。

是否索引:要索引。

是否存储:要存储。

图书价格:

是否分词:要分词,lucene对数字型的值只要有搜索需求的都要分词和索引,因 为lucene对数字型的内容要特殊分词处理,需要分词和索引。

是否索引:要索引

是否存储:要存储

图书图片地址:

是否分词:不分词

是否索引:不索引

是否存储:要存储

图书描述:

是否分词:要分词

是否索引:要索引

是否存储:因为图书描述内容量大,不在查询结果页面直接显示,不存储。

不存储是不在lucene的索引域中记录,节省lucene的索引文件空间。

如果要在详情页面显示描述,解决方案:

从lucene中取出图书的id,根据图书的id查询关系数据库(MySQL)中book表得到描述信息。

6.3.2. 代码修改

对之前编写的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));

7. 索引维护

7.1. 需求

管理人员使用lucene更新索引库商品数据。

7.2. 添加索引

调用 indexWriter.addDocument(doc)添加索引。

参考入门程序的创建索引。

7.3. 删除索引

7.3.1. 删除指定索引

根据Term项删除索引,满足条件的将全部删除。

@Test

public void testIndexDelete() throws Exception {

// 创建Directory流对象

Directory directory = FSDirectory.open(new File("C:/javahelp/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();

}

文档域数据被删除掉

7.3.2. 删除全部索引(慎用)

将索引目录的索引信息全部删除,直接彻底删除,无法恢复。

建议参照关系数据库基于主键删除方式,所以在创建索引时需要创建一个主键Field,删除时根据此主键Field删除。

索引删除后将放在Lucene的回收站中,Lucene3.X版本可以恢复删除的文档,3.X之后无法恢复。

代码:

@Test

public void testIndexDelete() throws Exception {

// 创建Directory流对象

Directory directory = FSDirectory.open(new File("D:/javahelp/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();

}

索引域数据清空

文档域数据也清空

7.4. 修改索引

更新索引是先删除再添加,建议对更新需求采用此方法并且要保证对已存在的索引执行更新,可以先查询出来,确定更新记录存在执行更新操作。

如果更新索引的目标文档对象不存在,则执行添加。

代码

@Test

public void testIndexUpdate() throws Exception {

// 创建分词器

Analyzer analyzer = new IKAnalyzer();

// 创建Directory流对象

Directory directory = FSDirectory.open(new File("C:/javahelp/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();

}

8. 搜索

8.1. 创建查询的两种方法

对要搜索的信息创建Query查询对象,Lucene会根据Query查询对象生成最终的查询语法。类似关系数据库Sql语法一样,Lucene也有自己的查询语法,比如:“name:lucene”表示查询名字为name的Field域中的“lucene”的文档信息。

可通过两种方法创建查询对象:

1)使用Lucene提供Query子类

Query是一个抽象类,lucene提供了很多查询对象,比如TermQuery项精确查询,NumericRangeQuery数字范围查询等,和组合查询BooleanQuery。

如下代码:

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");

8.2. 通过Query子类搜索

8.2.1. TermQuery

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:/javahelp/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();

}

8.2.2. NumericRangeQuery

NumericRangeQuery,指定数字范围查询.

@Test

public void testSearchNumericRangeQuery() throws Exception {

// 创建NumericRangeQuery搜索对象,数字范围查询.

// 五个参数分别是:域名、最小值、最大值、是否包含最小值,是否包含最大值

Query query = NumericRangeQuery.newFloatRange("price", 54f, 56f, false, true);

doSearch(query);

}

8.2.3. 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表示“或”的关系,即“并集”。

8.3. 通过QueryParser搜索

通过QueryParser也可以创建Query,QueryParser提供一个Parse方法,此方法可以直接根据查询语法来查询。可以通过打印Query对象的方式,查看生成的查询语句。

8.3.1. 查询语法(重点)

1.基础的查询语法,关键词查询:

域名+“:”+搜索的关键字

例如:name:java

2.范围查询

域名+“:”+[最小值 TO 最大值]

例如:size:[1 TO 1000]

注意:QueryParser不支持对数字范围的搜索,它支持字符串范围。数字范围搜索建议使用NumericRangeQuery。

3.组合条件查询

Occur.MUST 查询条件必须满足,相当于AND

+(加号)

Occur.SHOULD 查询条件可选,相当于OR

空(不用符号)

Occur.MUST_NOT 查询条件不能满足,相当于NOT非

-(减号)

8.3.2. 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);

}

8.3.3. 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

8.4. TopDocs

Lucene搜索结果可通过TopDocs遍历,TopDocs类提供了少量的属性,如下:

方法或属性

说明

totalHits

匹配搜索条件的总记录数

scoreDocs

顶部匹配记录

注意:

Search方法需要指定匹配记录数量n:indexSearcher.search(query, n)

TopDocs.totalHits:是匹配索引库中所有记录的数量

TopDocs.scoreDocs:匹配相关度高的前边记录数组,scoreDocs的长度小于等于search方法指定的参数n

9. 相关度排序(扩展)

9.1. 什么是相关度排序

相关度排序是查询结果按照与查询关键字的相关性进行排序,越相关的越靠前。比如搜索“Lucene”关键字,与该关键字最相关的文章应该排在前边。

9.2. 相关度打分

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)太普通,不足以区分这些文档,因而重要性越低。

9.3. 设置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:/javahelp/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在第一

查询结果:

10. Solr介绍

10.1. 什么是solr

Solr 是Apache下的一个顶级开源项目,采用Java开发,它是基于Lucene的全文搜索服务。Solr可以独立运行在Jetty、Tomcat等这些Servlet容器中。

使用Solr 进行创建索引和搜索索引的实现方法很简单,如下:

l 创建索引:客户端(可以是浏览器可以是Java程序)用 POST 方法向 Solr 服务器发送一个描述 Field 及其内容的 XML 文档,Solr服务器根据xml文档添加、删除、更新索引 。

l 搜索索引:客户端(可以是浏览器可以是Java程序)用 GET方法向 Solr 服务器发送请求,然后对 Solr服务器返回Xml、json等格式的查询结果进行解析。Solr不提供构建页面UI的功能。Solr提供了一个管理界面,通过管理界面可以查询Solr的配置和运行情况。

Solr是一个可以独立运行的搜索服务器,使用solr进行全文检索服务的话,只需要通过http请求访问该服务器即可。

10.2. Solr和Lucene的区别

Lucene是一个开放源代码的全文检索引擎工具包,它不是一个完整的全文检索应用。Lucene仅提供了完整的查询引擎和索引引擎,目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者以Lucene为基础构建全文检索应用。

Solr的目标是打造一款企业级的搜索引擎系统,它是基于Lucene一个搜索引擎服务,可以独立运行,通过Solr可以非常快速的构建企业的搜索引擎,通过Solr也可以高效的完成站内搜索功能。

11. Solr安装配置

11.1. 下载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相关的一些许可信息

11.2. 运行环境

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

11.3. SolrCore配置

11.3.1. SolrHome和SolrCore

SolrHome是Solr服务运行的主目录,该目录中包括了多个SolrCore目录。SolrCore目录中包含了运行Solr实例所有的配置文件和数据文件,Solr实例就是SolrCore。

每个SolrCore提供单独的搜索和索引服务。

11.3.1.1. 目录结构

SolrHome目录:

SolrCore目录:

11.3.2. 创建SolrCore

创建SolrCore先要创建SolrHome。在solr解压包下solr-4.10.3\example\solr文件夹就是一个标准的SolrHome,只需要将它复制到指定的目录下即可。

拷贝solr解压包下solr-4.10.3\example\solr文件夹。

复制该文件夹到本地的一个目录,把文件名称改为solrhome

改名不是必须的,只是为了便于理解

打开solrhome目录确认solrcore

11.3.3. 配置SolrCore

其实就是配置SolrCore目录下的conf/solrconfig.xml。

这个文件是来配置SolrCore实例的相关信息。如果使用默认配置可以不用做任何修改。它里面包含了不少标签,但是我们经常使用的标签为:lib标签、datadir标签、requestHandler标签。

11.3.3.1. lib 标签

在solrconfig.xml中可以加扩展载一些的jar,如果需要使用,则首先要把这些jar复制到指定的目录,我们复制到SolrHome同级目录

复制之前解压的文件夹中的contrib和dist文件夹

粘贴到SolrHome同级目录下。

修改solrconfig.xml配置文件加载扩展的jar。

configsolr.install.dir表示${SolrCore}的目录位置,需要如下修改:

./ 表示当前目录 ../表示上一级目录

11.3.3.2. datadir标签

配置SolrCore的data目录。

data目录用来存放SolrCore的索引文件和tlog日志文件

solr.data.dir表示${SolrCore}/data的目录位置

如果不想使用默认的目录也可以通过solrconfig.xml更改索引目录 ,

例如:

(建议不修改,否则配置多个SolrCore会报错)

11.3.3.3. 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>

11.4. Solr工程部署

由于在项目中用到的web服务器大多数是用的Tomcat,所以就进行solr和Tomcat的整合。

11.4.1. 安装Tomcat

复制自己的Tomcat7到这里

删除不用的应用(可以不删)

修改server.xml配置文件里面的端口号(否则后面eclipse使用Tomcat会冲突)

修改以下三个端口号

11.4.2. 部署solr.war到Tomcat中

1.从solr解压包下的solr-4.10.3\example\webapps目录中拷贝solr.war

复制solr.war

粘贴到自己Tomcat的webapps里

在Tomcat的webapps里,把war解压到当前路径,并删除solr.war

效果:

11.4.3. 添加solr服务的扩展jar包(日志包)

把solr解压包下solr-4.10.3\example\lib\ext目录下的所有jar包拷贝到Tomcat部署的solr的WEB-INF/lib文件夹

复制扩展jar包

粘贴到Tomcat的webapps的solr工程的WEB-INF\lib目录

11.4.4. 添加log4j.properties

把solr解压包下solr-4.10.3\example\resources\log4j.properties文件进行复制

粘贴到Tomcat的webapps的solr的WEB-INF\classes目录下

这里没有classes文件夹创建一个即可

11.4.5. 配置solr应用的web.xml

需要修改web.xml,让Tomcat使用JNDI的方式告诉solr服务器SolrHome在哪。

修改内容:

第42行的Solr/home名称必须是固定的,修改第43行,如下图

11.4.6. 启动Tomcat进行访问

访问

http://localhost:8081/solr/

出现以下界面则说明solr安装成功!!!

11.5. 管理界面功能介绍

11.5.1. Dashboard

仪表盘,显示了该Solr实例开始启动运行的时间、版本、系统资源、jvm等信息。

11.5.2. Logging

Solr运行日志信息

11.5.3. Cloud

Cloud即SolrCloud,即Solr云(集群),当使用Solr Cloud模式运行时会显示此菜单,该部分功能在第二个项目,即电商项目会演示。

11.5.4. Core Admin

Solr Core的管理界面。在这里可以添加SolrCore实例(有bug,不推荐使用浏览器界面添加SolrCore)。

11.5.5. java properties

Solr在JVM 运行环境中的属性信息,包括类路径、文件编码、jvm内存设置等信息。

11.5.6. Tread Dump

显示Solr Server中当前活跃线程信息,同时也可以跟踪线程运行栈信息。

11.5.7. Core selector

选择一个SolrCore进行详细操作,如下:

11.5.7.1. Analysis

通过此界面可以测试索引分析器和搜索分析器的执行情况

11.5.7.2. dataimport

可以定义数据导入处理器,从关系数据库将数据导入到Solr索引库中。

默认没有配置,需要手工配置。

11.5.7.3. Document

通过/update表示更新索引,solr默认根据id(唯一约束)域来更新Document的内容,如果根据id值搜索不到id域则会执行添加操作,如果找到则更新。

通过此菜单可以创建索引、更新索引、删除索引等操作,界面如下:

  • overwrite="true" : solr在做索引的时候,如果文档已经存在,就用xml中的文档进行替换
  • commitWithin="1000" : solr 在做索引的时候,每隔1000(1秒)毫秒,做一次文档提交。为了方便测试也可以在Document中立即提交,</doc>后添加“<commit/>”

11.5.7.4. Query

通过/select执行搜索索引,必须指定“q”查询条件方可搜索。

原文发布于微信公众号 - Java帮帮(javahelp)

原文发表时间:2018-07-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏后台架构

Sphinx源码学习笔记(一):索引创建

  因为项目开发需要在游戏内部实现玩家名称的模糊查找功能,本身直接使用Sphinx配置mysql可以直接搭建一套模糊匹配的即可支持功能的实现。

53170
来自专栏王硕

原 为PostgreSQL添加插件

69150
来自专栏ChaMd5安全团队

老司机带你过常规WAF

0x00 前言 最近看了不少关于WAF绕过的文章,想把理论深化到实践当中去,于是就有了您正在看的这篇文章,这篇文章分为两大部分,分别写的是SQL注入相关的WAF...

580110
来自专栏Android知识点总结

SpringBoot-07-之数据库JPA(CRUD)

14420
来自专栏逸鹏说道

SQL Server 存储过程的几种常见写法分析

最近发现还有不少做开发的小伙伴,在写存储过程的时候,在参考已有的不同的写法时,往往很迷茫, 不知道各种写法孰优孰劣,该选用那种写法,以及各种写法优缺点,本文以一...

43480
来自专栏liuchengxu

用 Python 写一个 NoSQL 数据库

本文译自 What is a NoSQL Database? Learn By Writing One In Python.

17330
来自专栏黑泽君的专栏

day28_Struts2综合案例

a、拷贝必要的jar包(图中黄色框框) 和 与数据库操作有关的jar包与配置文件(图中绿色框框)

10210
来自专栏分布式系统和大数据处理

数据库对象命名参考

编码规范是一个优秀程序员的必备素质,然而,有很多人非常注重程序中变量、方法、类的命名,却忽视了同样重要的数据库对象命名。这篇文章结合许多技术文章和资料,以及我自...

13320
来自专栏web编程技术分享

【手把手】JavaWeb 入门级项目实战 -- 文章发布系统 (第九节)

31860
来自专栏Java帮帮-微信公众号-技术文章全总结

day26.MySQL【Python教程】

15060

扫码关注云+社区

领取腾讯云代金券