mysql lucene怎么读_Lucene:一、入门

1 Lucene简介

Lucene是apache下的一个开源的全文检索引擎工具包。

1.1 全文检索(Full-text Search)

1.1.1 定义

全文检索就是先分词创建索引,再执行搜索的过程。

分词:就是将一段文字分成一个个单词

全文检索就将一段文字分成一个个单词去查询数据!!!

1.1.2 应用场景

1.1.2.1 搜索引擎(了解)

搜索引擎是一个基于全文检索、能独立运行、提供搜索服务的软件系统。

ca2b2f5149640ade70a5fe2d549783e5.png

1.2 Lucene实现全文检索的流程

83080e7fcc0b3a5f4f9edba4b524f30a.png

全文检索的流程分为两大部分:索引流程、搜索流程。

索引流程:采集数据--->构建文档对象--->创建索引(将文档写入索引库)。

搜索流程:创建查询--->执行搜索--->渲染搜索结果。

2 入门示例

2.1 需求

使用Lucene实现电商项目中图书类商品的索引和搜索功能。

2.2 配置步骤说明

(1)搭建环境(先下载Lucene)

(2)创建索引库

(3)搜索索引库

2.3 配置步骤

2.3.1 第一部分:搭建环境(创建项目,导入包)

前提:已经创建好了数据库(直接导入book.sql文件)

43fea02ba640f0a5a398447b110a196f.png

2.3.1.1 第一步:下载Lucene

Lucene是开发全文检索功能的工具包,使用时从官方网站下载,并解压。

下载版本:4.10.3(要求:jdk1.7及以上)

核心包lucene-core-4.10.3.jar(附常用API)

15bcf4215bffe41f914d285c92df60c0.png

2.3.1.2 第二步:创建项目,导入包

mysql5.1驱动包:mysql-connector-java-5.1.7-bin.jar

核心包:lucene-core-4.10.3.jar

分析器通用包:lucene-analyzers-common-4.10.3.jar

查询解析器包:lucene-queryparser-4.10.3.jar

项目结构如下:

476e8f519000c968091234acb43850e5.png

2.3.2 第二部分:创建索引

步骤说明:

(1)采集数据

(2)将数据转换成Lucene文档

(3)将文档写入索引库,创建索引

2.3.2.1 第一步:采集数据

Lucene全文检索,不是直接查询数据库,所以需要先将数据采集出来。

(1)创建Book类

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 public classBook {2

3   private Integer bookId; //图书ID

4

5   private String name; //图书名称

6

7   private Float price; //图书价格

8

9   private String pic; //图书图片

10

11   private String description; //图书描述12

13   //补全get\set方法

14

15 }

View Code

public class Book {

private Integer bookId; // 图书ID

private String name; // 图书名称

private Float price; // 图书价格

private String pic; // 图书图片

private String description; // 图书描述

// 补全get\set方法

}

(2)创建一个BookDao类

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecn.gzsxt.lucene.dao;2

3 importjava.sql.Connection;4

5 importjava.sql.DriverManager;6

7 importjava.sql.PreparedStatement;8

9 importjava.sql.ResultSet;10

11 importjava.sql.SQLException;12

13 importjava.util.ArrayList;14

15 importjava.util.List;16

17 importcn.gzsxt.lucene.pojo.Book;18

19 public classBookDao {20

21   public ListgetAll() {22

23     //数据库链接

24

25     Connection connection = null;26

27     //预编译statement

28

29     PreparedStatement preparedStatement = null;30

31     //结果集

32

33     ResultSet resultSet = null;34

35     //图书列表

36

37     List list = new ArrayList();38

39     try{40

41       //加载数据库驱动

42

43       Class.forName("com.mysql.jdbc.Driver");44

45       //连接数据库

46

47       connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/lucene", "root", "gzsxt");48

49       //SQL语句

50

51       String sql = "SELECT * FROM book";52

53       //创建preparedStatement

54

55       preparedStatement =connection.prepareStatement(sql);56

57       //获取结果集

58

59       resultSet =preparedStatement.executeQuery();60

61       //结果集解析

62

63       while(resultSet.next()) {64

65         Book book = newBook();66

67         book.setBookId(resultSet.getInt("id"));68

69         book.setName(resultSet.getString("name"));70

71         book.setPrice(resultSet.getFloat("price"));72

73         book.setPic(resultSet.getString("pic"));74

75         book.setDescription(resultSet.getString("description"));76

77 list.add(book);78

79 }80

81     } catch(Exception e) {82

83 e.printStackTrace();84

85     }finally{86

87       if(null!=resultSet){88

89         try{90

91 resultSet.close();92

93         } catch(SQLException e) {94

95           //TODO Auto-generated catch block

96

97 e.printStackTrace();98

99 }100

101 }102

103       if(null!=preparedStatement){104

105         try{106

107 preparedStatement.close();108

109         } catch(SQLException e) {110

111           //TODO Auto-generated catch block

112

113 e.printStackTrace();114

115 }116

117 }118

119       if(null!=connection){120

121         try{122

123 connection.close();124

125         } catch(SQLException e) {126

127           //TODO Auto-generated catch block

128

129 e.printStackTrace();130

131 }132

133 }134

135 }136

137     returnlist;138

139 }140

141 }

View Code

package cn.gzsxt.lucene.dao;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.util.ArrayList;

import java.util.List;

import cn.gzsxt.lucene.pojo.Book;

public class BookDao {

public List getAll() {

// 数据库链接

Connection connection = null;

// 预编译statement

PreparedStatement preparedStatement = null;

// 结果集

ResultSet resultSet = null;

// 图书列表

List list = new ArrayList();

try {

// 加载数据库驱动

Class.forName("com.mysql.jdbc.Driver");

// 连接数据库

connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/lucene", "root", "gzsxt");

// SQL语句

String sql = "SELECT * FROM book";

// 创建preparedStatement

preparedStatement = connection.prepareStatement(sql);

// 获取结果集

resultSet = preparedStatement.executeQuery();

// 结果集解析

while (resultSet.next()) {

Book book = new Book();

book.setBookId(resultSet.getInt("id"));

book.setName(resultSet.getString("name"));

book.setPrice(resultSet.getFloat("price"));

book.setPic(resultSet.getString("pic"));

book.setDescription(resultSet.getString("description"));

list.add(book);

}

} catch (Exception e) {

e.printStackTrace();

}finally {

if(null!=resultSet){

try {

resultSet.close();

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

if(null!=preparedStatement){

try {

preparedStatement.close();

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

if(null!=connection){

try {

connection.close();

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

return list;

}

}

(3)创建一个测试类BookDaoTest

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecn.gzsxt.lucene.test;2

3 importjava.util.List;4

5 importorg.junit.Test;6

7 importcn.gzsxt.lucene.dao.BookDao;8

9 importcn.gzsxt.lucene.pojo.Book;10

11 public classBookDaoTest {12

13 @Test14

15 public voidgetAll(){16

17 BookDao dao = newBookDao();18

19 List books =dao.getAll();20

21 for(Book book : books) {22

23 System.out.println("图书id:"+book.getBookId()+",图书名称:"+book.getName());24

25 }26

27 }28

29 }

View Code

package cn.gzsxt.lucene.test;

import java.util.List;

import org.junit.Test;

import cn.gzsxt.lucene.dao.BookDao;

import cn.gzsxt.lucene.pojo.Book;

public class BookDaoTest {

@Test

public void getAll(){

BookDao dao = new BookDao();

List books = dao.getAll();

for (Book book : books) {

System.out.println("图书id:"+book.getBookId()+",图书名称:"+book.getName());

}

}

}

(4)测试结果,采集数据成功

d152743c9f2e69fb7460f3298b32e105.png

2.3.2.2 第二步:将数据转换成Lucene文档

Lucene是使用文档类型来封装数据的,所有需要先将采集的数据转换成文档类型。其格式为:

048a84e350a2545016894cebeca1aee6.png

修改BookDao,新增一个方法,转换数据

public List getDocuments(List books){

// Document对象集合

List docList = new ArrayList();

// Document对象

Document doc = null;

for (Book book : books) {

// 创建Document对象,同时要创建field对象

doc = new Document();

// 根据需求创建不同的Field

Field id = new TextField("id", book.getBookId().toString(), Store.YES);

Field name = new TextField("name", book.getName(), Store.YES);

Field price = new TextField("price", book.getPrice().toString(),Store.YES);

Field pic = new TextField("pic", book.getPic(), Store.YES);

Field desc = new TextField("description", book.getDescription(), Store.YES);

// 把域(Field)添加到文档(Document)中

doc.add(id);

doc.add(name);

doc.add(price);

doc.add(pic);

doc.add(desc);

docList.add(doc);

}

return docList;

}

2.3.2.3 第三步:创建索引库

说明:Lucene是在将文档写入索引库的过程中,自动完成分词、创建索引的。因此创建索引库,从形式上看,就是将文档写入索引库!

修改测试类,新增createIndex方法

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 @Test2

3 public voidcreateIndex(){4

5   try{6

7     BookDao dao = newBookDao();8

9     //分析文档,对文档中的field域进行分词

10

11     Analyzer analyzer = newStandardAnalyzer();12

13     //创建索引14

15     //1) 创建索引库目录

16

17     Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));18

19     //2) 创建IndexWriterConfig对象

20

21     IndexWriterConfig cfg = newIndexWriterConfig(Version.LATEST, analyzer);22

23     //3) 创建IndexWriter对象

24

25     IndexWriter writer = newIndexWriter(directory, cfg);26

27     //4) 通过IndexWriter对象添加文档对象(document)

28

29 writer.addDocuments(dao.getDocuments(dao.getAll()));30

31     //5) 关闭IndexWriter

32

33 writer.close();34

35     System.out.println("创建索引库成功");36

37   } catch(Exception e) {38

39 e.printStackTrace();40

41 }42

43 }

View Code

@Test

public void createIndex(){

try {

BookDao dao = new BookDao();

// 分析文档,对文档中的field域进行分词

Analyzer analyzer = new StandardAnalyzer();

// 创建索引

// 1) 创建索引库目录

Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));

// 2) 创建IndexWriterConfig对象

IndexWriterConfig cfg = new IndexWriterConfig(Version.LATEST, analyzer);

// 3) 创建IndexWriter对象

IndexWriter writer = new IndexWriter(directory, cfg);

// 4) 通过IndexWriter对象添加文档对象(document)

writer.addDocuments(dao.getDocuments(dao.getAll()));

// 5) 关闭IndexWriter

writer.close();

System.out.println("创建索引库成功");

} catch (Exception e) {

e.printStackTrace();

}

}

测试结果,创建成功!!!

c89e5f337a210759b167ba9e7894e65a.png

2.3.3 第三部分:搜索索引

2.3.3.1 说明

搜索的时候,需要指定搜索哪一个域(也就是字段),并且,还要对搜索的关键词做分词处理。

2.3.3.2 执行搜索

修改测试类,新增searchDocumentByIndex方法

1 @Test2

3 public voidsearchDocumentByIndex(){4

5   try{6

7     //1、 创建查询(Query对象)8

9     //创建分析器

10

11     Analyzer analyzer = newStandardAnalyzer();12

13     QueryParser queryParser = new QueryParser("name", analyzer);14

15     Query query = queryParser.parse("name:java教程");16

17     //2、 执行搜索18

19     //a) 指定索引库目录

20

21     Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));22

23     //b) 创建IndexReader对象

24

25     IndexReader reader =DirectoryReader.open(directory);26

27     //c) 创建IndexSearcher对象

28

29     IndexSearcher searcher = newIndexSearcher(reader);30

31     //d) 通过IndexSearcher对象执行查询索引库,返回TopDocs对象32

33     //第一个参数:查询对象34

35     //第二个参数:最大的n条记录

36

37     TopDocs topDocs = searcher.search(query, 10);38

39     //e) 提取TopDocs对象中前n条记录

40

41     ScoreDoc[] scoreDocs =topDocs.scoreDocs;42

43     System.out.println("查询出文档个数为:" +topDocs.totalHits);44

45     for(ScoreDoc scoreDoc : scoreDocs) {46

47       //文档对象ID

48

49       int docId =scoreDoc.doc;50

51       Document doc =searcher.doc(docId);52

53       //f) 输出文档内容

54

55       System.out.println("===============================");56

57       System.out.println("文档id:" +docId);58

59       System.out.println("图书id:" + doc.get("id"));60

61       System.out.println("图书name:" + doc.get("name"));62

63       System.out.println("图书price:" + doc.get("price"));64

65       System.out.println("图书pic:" + doc.get("pic"));66

67       System.out.println("图书description:" + doc.get("description"));68

69 }70

71     //g) 关闭IndexReader

72

73 reader.close();74

75   } catch(Exception e) {76

77     //TODO Auto-generated catch block

78

79 e.printStackTrace();80

81 }82

83 }

@Test

public void searchDocumentByIndex(){

try {

// 1、 创建查询(Query对象)

// 创建分析器

Analyzer analyzer = new StandardAnalyzer();

QueryParser queryParser = new QueryParser("name", analyzer);

Query query = queryParser.parse("name:java教程");

// 2、 执行搜索

// a) 指定索引库目录

Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));

// b) 创建IndexReader对象

IndexReader reader = DirectoryReader.open(directory);

// c) 创建IndexSearcher对象

IndexSearcher searcher = new IndexSearcher(reader);

// d) 通过IndexSearcher对象执行查询索引库,返回TopDocs对象

// 第一个参数:查询对象

// 第二个参数:最大的n条记录

TopDocs topDocs = searcher.search(query, 10);

// e) 提取TopDocs对象中前n条记录

ScoreDoc[] scoreDocs = topDocs.scoreDocs;

System.out.println("查询出文档个数为:" + topDocs.totalHits);

for (ScoreDoc scoreDoc : scoreDocs) {

// 文档对象ID

int docId = scoreDoc.doc;

Document doc = searcher.doc(docId);

// f) 输出文档内容

System.out.println("===============================");

System.out.println("文档id:" + docId);

System.out.println("图书id:" + 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("图书description:" + doc.get("description"));

}

// g) 关闭IndexReader

reader.close();

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

测试结果,非常成功!!!

b31890263bae64fc17857d9030696085.png

2.4 小结

Lucene全文检索,确实可以实现对关键词做分词、再执行搜索功能。并且结果更精确。

3 分词

3.1 重要性

分词是全文检索的核心。

所谓的分词,就是将一段文本,根据一定的规则,拆分成一个一个词。

Lucene是根据分析器实现分词的。针对不同的语言提供了不同的分析器。并且提供了一个通用的标准分析器StandardAnalyzer

3.2 分词过程

--说明:我们通过分析StandardAnalyzer核心源码来分析分词过程

1 @Override2

3 protected TokenStreamComponents createComponents(final String fieldName, finalReader reader) {4

5   final StandardTokenizer src = newStandardTokenizer(getVersion(), reader);6

7 src.setMaxTokenLength(maxTokenLength);8

9   TokenStream tok = newStandardFilter(getVersion(), src);10

11   tok = newLowerCaseFilter(getVersion(), tok);12

13   tok = newStopFilter(getVersion(), tok, stopwords);14

15   return newTokenStreamComponents(src, tok) {16

17 @Override18

19     protected void setReader(final Reader reader) throwsIOException {20

21     src.setMaxTokenLength(StandardAnalyzer.this.maxTokenLength);22

23     super.setReader(reader);24

25 }26

27 };28

29 }

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

}

};

}

对应Lucene分词的过程,我们可以做如下总结:

(1)分词的时候,是以域为单位的。不同的域,相互独立。

同一个域中,拆分出来相同的词,视为同一个词(Term)

不同的域中,拆分出来相同的词,不是同一个词。由下图可以看出,在name域和desc域中都有java。

其中,Term是Lucene最小的语汇单元,不可再细分。

(2)分词的时候经历了一系列的过滤器。如大小写转换、去除停用词等。

3.3 分词后索引库结构

我们这里借助前面的示例来说明

a03717141df511e89f7666192beb5bdb.png

从上图中,我们发现:

(1)索引库中有两个区域:索引区、文档区。

(2)文档区存放的是文档。Lucene给每一个文档自动加上一个文档编号docID。

(3)索引区存放的是索引。注意:

索引是以域为单位的,不同的域,彼此相互独立。

索引是根据分词规则创建出来的,根据索引就能找到对应的文档。

3.4 Luke客户端连接索引库

Luke作为Lucene工具包中的一个工具(http://www.getopt.org/luke/),可以通过可视化界面,连接操作索引库。

3.4.1 启动方法

(1)双击start.bat启动!

0a492b71301491e275fee1af654add37.png

(2)连接索引库

5d7e703519e78de52fb35935c49ac28a.png

3.4.2 验证分词效果

434aa0e2c7933193654a9de44fe05453.png

4 Field域

问题:我们已经知道,Lucene是在写入文档时,完成分词、索引的。那Lucene是怎么知道如何分词的呢?

答:Lucene是根据文档中的域的属性,来确定是否要分词、创建索引的。所以,我们必须搞清楚域有哪些属性。

4.1 域的属性

4.1.1 三大属性

4.1.1.1 是否分词(tokenized)

只有设置了分词属性为true,lucene才会对这个域进行分词处理。

在实际的开发中,有一些字段是不需要分词的,比如商品id,商品图片等。

而有一些字段是必须分词的,比如商品名称,描述信息等。

4.1.1.2 是否索引(indexed)

只有设置了索引属性为true,lucene才为这个域的Term词创建索引。

在实际的开发中,有一些字段是不需要创建索引的,比如商品的图片等。我们只需要对参与搜索的字段做索引处理。

4.1.1.3 是否存储(stored)

只有设置了存储属性为true,在查找的时候,才能从文档中获取这个域的值。

在实际开发中,有一些字段是不需要存储的。比如:商品的描述信息。

因为商品描述信息,通常都是大文本数据,读的时候会造成巨大的IO开销。而描述信息是不需要经常查询的字段,这样的话就白白浪费了cpu的资源了。

因此,像这种不需要经常查询,又是大文本的字段,通常不会存储到索引库。

4.1.2 特点

(1)三大属性彼此独立。

(2)通常分词是为了创建索引。

(3)不存储这个域文本内容,也可以对这个域先分词、创建索引。

4.2 Field常用类型

域的常用类型有很多,每一个类都有自己默认的三大属性。如下:

Field类

数据类型

Analyzed

是否分词

Indexed

是否索引

Stored

是否存储

StringField(FieldName, FieldValue,Store.YES))

字符串

N

Y

Y或N

LongField(FieldName, FieldValue,Store.YES)

Long型

Y

Y

Y或N

FloatField(FieldName, FieldValue,Store.YES)

Float型

Y

Y

Y或N

StoredField(FieldName, FieldValue)

重载方法,支持多种类型

N

N

Y

TextField(FieldName, FieldValue, Store.NO)

字符串

Y

Y

Y或N

(不单单是这些,还有像DoubleField等等)

4.3 改造入门示例中的域类型

4.3.1 分析

(1)图书id:

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

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

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

(2)图书名称:

是否分词:要分词,因为要将图书的名称内容分词索引,根据关键搜索图书名称抽取的词。

是否索引:要索引。

是否存储:要存储。

(3)图书价格:

是否分词:要分词,lucene对数字型的值只要有搜索需求的都要分词和索引,因为lucene对数字型的内容要特殊分词处理,本例子可能要根据价格范围搜索, 需要分词和索引。

是否索引:要索引

是否存储:要存储

(4)图书图片地址:

是否分词:不分词

是否索引:不索引

是否存储:要存储

(5)图书描述:

是否分词:要分词

是否索引:要索引

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

不存储是来不在lucene的索引文件中记录,节省lucene的索引文件空间,如果要在详情页面显示描述,思路:

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

4.3.2 代码修改

修改BookDao的getDocument方法

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 public List getDocuments(Listbooks){2

3   //Document对象集合

4

5   List docList = new ArrayList();6

7   //Document对象

8

9   Document doc = null;10

11   for(Book book : books) {12

13     //创建Document对象,同时要创建field对象

14

15     doc = newDocument();16

17     //图书ID18

19     //参数:域名、域中存储的内容、是否存储20

21     //不分词、索引、要存储22

23     //Field id = new TextField("id", book.getId().toString(),Store.YES);

24

25     Field id = new StoredField("id", book.getBookId().toString());26

27     //图书名称28

29     //分词、索引、存储

30

31     Field name = new TextField("name", book.getName(),Store.YES);32

33     //图书价格34

35     //分词、索引、存储

36

37     Field price = new FloatField("price", book.getPrice(), Store.YES);38

39     //图书图片40

41     //不分词、不索引、要存储

42

43     Field pic = new StoredField("pic", book.getPic());44

45     //图书描述46

47     //分词、索引、不存储

48

49     Field desc = new TextField("description",book.getDescription(), Store.NO);50

51     //把域(Field)添加到文档(Document)中

52

53 doc.add(id);54

55 doc.add(name);56

57 doc.add(price);58

59 doc.add(pic);60

61 doc.add(desc);62

63 docList.add(doc);64

65 }66

67   returndocList;68

69 }

View Code

public List getDocuments(List books){

// Document对象集合

List docList = new ArrayList();

// Document对象

Document doc = null;

for (Book book : books) {

// 创建Document对象,同时要创建field对象

doc = new Document();

// 图书ID

// 参数:域名、域中存储的内容、是否存储

// 不分词、索引、要存储

// Field id = new TextField("id", book.getId().toString(),Store.YES);

Field id =newStoredField("id", book.getBookId().toString());

// 图书名称

// 分词、索引、存储

Field name =newTextField("name", book.getName(),Store.YES);

// 图书价格

// 分词、索引、存储

Field price =newFloatField("price", book.getPrice(), Store.YES);

// 图书图片

// 不分词、不索引、要存储

Field pic =newStoredField("pic", book.getPic());

// 图书描述

// 分词、索引、不存储

Field desc =newTextField("description",book.getDescription(), Store.NO);

// 把域(Field)添加到文档(Document)中

doc.add(id);

doc.add(name);

doc.add(price);

doc.add(pic);

doc.add(desc);

docList.add(doc);

}

return docList;

}

4.3.3 测试

(1)去索引库目录中,手动清空索引库。

(2)重新创建索引库。

(3)使用Luke验证分词、索引效果。

2b09e029cd2c98e03abf65181030153b.png

改造成功!!!

5 索引库维护

在第4节,我们需要重新创建索引的时候,是去索引库目录下,手动删除的。

而在实际的开发中,我们可能压根就不知道索引库在哪,就算知道,我们也不可能每次都去手动删除,非常之麻烦!!!

所以,我们必须学习如何维护索引库,使用程序来操作索引库。

需要注意的是,索引是与文档紧密相连的,因此对索引的维护,实际上就是对文档的增删改。

5.1 添加索引(文档)

5.1.1 需求

数据库中新上架了图书,必须把这些图书也添加到索引库中,不然就搜不到该新上架的图书了。

5.1.2 代码实现

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

参考入门示例中的创建索引。

5.2 删除索引(文档)

5.2.1 需求

某些图书不再出版销售了,我们需要从索引库中移除该图书。

5.2.2 代码实现

1@Test2

3 public void deleteIndex() throwsException {4

5   //1、指定索引库目录

6

7   Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));8

9   //2、创建IndexWriterConfig

10

11   IndexWriterConfig cfg = newIndexWriterConfig(Version.LATEST,12

13   newStandardAnalyzer());14

15   //3、 创建IndexWriter

16

17   IndexWriter writer = newIndexWriter(directory, cfg);18

19   //4、通过IndexWriter来删除索引20

21   //删除指定索引

22

23   writer.deleteDocuments(new Term("name", "apache"));24

25   //5、关闭IndexWriter

26

27 writer.close();28

29   System.out.println("删除成功");30

31 }

@Test

public void deleteIndex() throws Exception {

// 1、指定索引库目录

Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));

// 2、创建IndexWriterConfig

IndexWriterConfig cfg = new IndexWriterConfig(Version.LATEST,

new StandardAnalyzer());

// 3、 创建IndexWriter

IndexWriter writer = new IndexWriter(directory, cfg);

// 4、通过IndexWriter来删除索引

// 删除指定索引

writer.deleteDocuments(newTerm("name", "apache"));

// 5、关闭IndexWriter

writer.close();

System.out.println("删除成功");

}

5.2.3 清空索引库

1@Test2

3 public void deleteIndex() throwsException {4

5   //1、指定索引库目录

6

7   Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));8

9   //2、创建IndexWriterConfig

10

11   IndexWriterConfig cfg = newIndexWriterConfig(Version.LATEST,12

13   newStandardAnalyzer());14

15   //3、 创建IndexWriter

16

17   IndexWriter writer = newIndexWriter(directory, cfg);18

19   //4、通过IndexWriter来删除索引20

21   //删除指定索引

22

23 writer.deleteAll();24

25   //5、关闭IndexWriter

26

27 writer.close();28

29   System.out.println("清空索引库成功");30

31 }

@Test

public void deleteIndex() throws Exception {

// 1、指定索引库目录

Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));

// 2、创建IndexWriterConfig

IndexWriterConfig cfg = new IndexWriterConfig(Version.LATEST,

new StandardAnalyzer());

// 3、 创建IndexWriter

IndexWriter writer = new IndexWriter(directory, cfg);

// 4、通过IndexWriter来删除索引

// 删除指定索引

writer.deleteAll();

// 5、关闭IndexWriter

writer.close();

System.out.println("清空索引库成功");

}

5.3 更新索引(文档)

5.3.1 说明

Lucene更新索引比较特殊,是先删除满足条件的索引,再添加新的索引。

5.3.2 代码实现

1 //修改索引

2

3 @Test4

5 public void updateIndex() throwsException {6

7   //1、指定索引库目录

8

9   Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));10

11   //2、创建IndexWriterConfig

12

13   IndexWriterConfig cfg = newIndexWriterConfig(Version.LATEST,14

15   newStandardAnalyzer());16

17   //3、 创建IndexWriter

18

19   IndexWriter writer = newIndexWriter(directory, cfg);20

21   //4、通过IndexWriter来修改索引22

23   //a)、创建修改后的文档对象

24

25   Document document = newDocument();26

27   //文件名称

28

29   Field filenameField = new StringField("name", "updateIndex", Store.YES);30

31 document.add(filenameField);32

33   //修改指定索引为新的索引

34

35   writer.updateDocument(new Term("name", "apache"), document);36

37   //5、关闭IndexWriter

38

39 writer.close();40

41   System.out.println("更新成功");42

43 }

// 修改索引

@Test

public void updateIndex() throws Exception {

// 1、指定索引库目录

Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));

// 2、创建IndexWriterConfig

IndexWriterConfig cfg = new IndexWriterConfig(Version.LATEST,

new StandardAnalyzer());

// 3、 创建IndexWriter

IndexWriter writer = new IndexWriter(directory, cfg);

// 4、通过IndexWriter来修改索引

// a)、创建修改后的文档对象

Document document = new Document();

// 文件名称

Field filenameField = new StringField("name", "updateIndex", Store.YES);

document.add(filenameField);

// 修改指定索引为新的索引

writer.updateDocument(new Term("name", "apache"), document);

// 5、关闭IndexWriter

writer.close();

System.out.println("更新成功");

}

6 搜索

问题:我们在入门示例中,已经知道Lucene是通过IndexSearcher对象,来执行搜索的。那我们为什么还要继续学习Lucene的查询呢?

答:因为在实际的开发中,我们的查询的业务是相对复杂的,比如我们在通过关键词查找的时候,往往进行价格、商品类别的过滤。

而Lucene提供了一套查询方案,供我们实现复杂的查询。

6.1 创建查询的两种方法

执行查询之前,必须创建一个查询Query查询对象。

Query自身是一个抽象类,不能实例化,必须通过其它的方式来实现初始化。

在这里,Lucene提供了两种初始化Query查询对象的方式。

6.1.1 使用Lucene提供Query子类

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

使用TermQuery实例化

Query query = new TermQuery(new Term("name", "lucene"));

6.1.2 使用QueryParse解析查询表达式

QueryParser会将用户输入的查询表达式解析成Query对象实例。如下代码:

QueryParser queryParser = new QueryParser("name", new IKAnalyzer());

Query query = queryParser.parse("name:lucene");

6.2 常用的Query子类搜索

6.2.1 TermQuery

特点:查询的关键词不会再做分词处理,作为整体来搜索。代码如下:

1 /**

2

3 * Query子类查询之 TermQuery4

5 *6

7 * 特点:不会再对查询的关键词做分词处理。8

9 *10

11 * 需要:查询书名与java教程相关书。12

13 */

14

15 @Test16

17 public voidqueryByTermQuery(){18

19   //1、获取一个查询对象

20

21   Query query = new TermQuery(new Term("name", "编程思想"));22

23 doSearch(query);24

25 }26

27 private voiddoSearch(Query query) {28

29   try{30

31     //2、创建一个查询的执行对象32

33     //指定索引库的目录

34

35     Directory d = FSDirectory.open(new File("F:\\lucene\\0719"));36

37     //创建流对象

38

39     IndexReader reader =DirectoryReader.open(d);40

41     //创建搜索执行对象

42

43     IndexSearcher searcher = newIndexSearcher(reader);44

45     //3、执行搜索

46

47     TopDocs result = searcher.search(query, 10);48

49     //4、提出结果集,获取图书的信息

50

51     int totalHits =result.totalHits;52

53     System.out.println("共查询到"+totalHits+"条满足条件的数据!");54

55     System.out.println("-----------------------------------------");56

57     //提取图书信息。58

59     //score即相关度。即搜索的关键词和 图书名称的相关度,用来做排序处理

60

61     ScoreDoc[] scoreDocs =result.scoreDocs;62

63     for(ScoreDoc scoreDoc : scoreDocs) {64

65       /**

66

67 * scoreDoc.doc的返回值,是文档的id, 即 将文档写入索引库的时候,lucene自动给这份文档做的一个编号。68

69 *70

71 * 获取到这个文档id之后,即可以根据这个id,找到这份文档。72

73       */

74

75       int docId =scoreDoc.doc;76

77       System.out.println("文档在索引库中的编号:"+docId);78

79       //从文档中提取图书的信息

80

81       Document doc =searcher.doc(docId);82

83       System.out.println("图书id:"+doc.get("id"));84

85       System.out.println("图书name:"+doc.get("name"));86

87       System.out.println("图书price:"+doc.get("price"));88

89       System.out.println("图书pic:"+doc.get("pic"));90

91       System.out.println("图书description:"+doc.get("description"));92

93 System.out.println();94

95       System.out.println("------------------------------------");96

97 }98

99     //关闭连接,释放资源

100

101     if(null!=reader){102

103 reader.close();104

105 }106

107   } catch(Exception e) {108

109 e.printStackTrace();110

111 }112

113 }

/**

* Query子类查询之 TermQuery

*

* 特点:不会再对查询的关键词做分词处理。

*

* 需要:查询书名与java教程相关书。

*/

@Test

public void queryByTermQuery(){

//1、获取一个查询对象

Query query = new TermQuery(new Term("name", "编程思想"));

doSearch(query);

}

private void doSearch(Query query) {

try {

//2、创建一个查询的执行对象

//指定索引库的目录

Directory d = FSDirectory.open(new File("F:\\lucene\\0719"));

//创建流对象

IndexReader reader = DirectoryReader.open(d);

//创建搜索执行对象

IndexSearcher searcher = new IndexSearcher(reader);

//3、执行搜索

TopDocs result = searcher.search(query, 10);

//4、提出结果集,获取图书的信息

int totalHits = result.totalHits;

System.out.println("共查询到"+totalHits+"条满足条件的数据!");

System.out.println("-----------------------------------------");

//提取图书信息。

//score即相关度。即搜索的关键词和 图书名称的相关度,用来做排序处理

ScoreDoc[] scoreDocs = result.scoreDocs;

for (ScoreDoc scoreDoc : scoreDocs) {

/**

* scoreDoc.doc的返回值,是文档的id, 即 将文档写入索引库的时候,lucene自动给这份文档做的一个编号。

*

* 获取到这个文档id之后,即可以根据这个id,找到这份文档。

*/

int docId = scoreDoc.doc;

System.out.println("文档在索引库中的编号:"+docId);

//从文档中提取图书的信息

Document doc = searcher.doc(docId);

System.out.println("图书id:"+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("图书description:"+doc.get("description"));

System.out.println();

System.out.println("------------------------------------");

}

//关闭连接,释放资源

if(null!=reader){

reader.close();

}

} catch (Exception e) {

e.printStackTrace();

}

}

6.2.2 NumericRangeQuery

指定数字范围查询.(创建field类型时,注意与之对应)

1 /**

2

3 * Query子类查询 之 NumricRangeQuery4

5 * 需求:查询所有价格在[60,80)之间的书6

7 *@paramquery8

9 */

10

11 @Test12

13 public voidqueryByNumricRangeQuery(){14

15   /**

16

17 * 第一个参数:要搜索的域18

19 * 第二个参数:最小值20

21 * 第三个参数:最大值22

23 * 第四个参数:是否包含最小值24

25 * 第五个参数:是否包含最大值26

27   */

28

29   Query query = NumericRangeQuery.newFloatRange("price", 60.0f, 80.0f, true, false);30

31 doSearch(query);32

33 }

/**

* Query子类查询  之  NumricRangeQuery

* 需求:查询所有价格在[60,80)之间的书

* @param query

*/

@Test

public void queryByNumricRangeQuery(){

/**

* 第一个参数:要搜索的域

* 第二个参数:最小值

* 第三个参数:最大值

* 第四个参数:是否包含最小值

* 第五个参数:是否包含最大值

*/

Query query = NumericRangeQuery.newFloatRange("price", 60.0f, 80.0f, true, false);

doSearch(query);

}

6.2.3 BooleanQuery

BooleanQuery,布尔查询,实现组合条件查询。

1 /**

2

3 * Query子类查询 之 BooelanQuery查询 组合条件查询4

5 *6

7 * 需求:查询书名包含java,并且价格区间在[60,80)之间的书。8

9 */

10

11 @Test12

13 public voidqueryBooleanQuery(){14

15   //1、要使用BooelanQuery查询,首先要把单个创建出来,然后再通过BooelanQuery组合

16

17   Query price = NumericRangeQuery.newFloatRange("price", 60.0f, 80.0f, true, false);18

19   Query name = new TermQuery(new Term("name", "java"));20

21   //2、创建BooleanQuery实例对象

22

23   BooleanQuery query = newBooleanQuery();24

25 query.add(name, Occur.MUST_NOT);26

27 query.add(price, Occur.MUST);28

29   /**

30

31 * MSUT 表示必须满足 对应的是 +32

33 * MSUT_NOT 必须不满足 应对的是 -34

35 * SHOULD 可以满足也可以不满足 没有符号36

37 *38

39 * SHOULD 与MUST、MUST_NOT组合的时候,SHOULD就没有意义了。40

41   */

42

43 doSearch(query);44

45 }

/**

* Query子类查询  之  BooelanQuery查询   组合条件查询

*

* 需求:查询书名包含java,并且价格区间在[60,80)之间的书。

*/

@Test

public void queryBooleanQuery(){

//1、要使用BooelanQuery查询,首先要把单个创建出来,然后再通过BooelanQuery组合

Query price = NumericRangeQuery.newFloatRange("price", 60.0f, 80.0f, true, false);

Query name = new TermQuery(new Term("name", "java"));

//2、创建BooleanQuery实例对象

BooleanQuery query = new BooleanQuery();

query.add(name, Occur.MUST_NOT);

query.add(price, Occur.MUST);

/**

* MSUT  表示必须满足                          对应的是  +

* MSUT_NOT  必须不满足                   应对的是  -

* SHOULD  可以满足也可以不满足     没有符号

*

* SHOULD 与MUST、MUST_NOT组合的时候,SHOULD就没有意义了。

*/

doSearch(query);

}

6.3 通过QueryParser搜索

6.3.1 特点

对搜索的关键词,做分词处理。

6.3.2 语法

6.3.2.1 基础语法

域名:关键字

实例:name:java

6.3.2.2 组合条件语法

条件1 AND 条件2

条件1 OR 条件2

条件1 NOT 条件2

6.3.3 QueryParser

6.3.3.1 代码实现

1 /**

2

3 * 查询解析器查询 之 QueryParser查询4

5 */

6

7 @Test8

9 public voidqueryByQueryParser(){10

11   try{12

13     //1、加载分词器

14

15     Analyzer analyzer = newStandardAnalyzer();16

17     /**

18

19 * 2、创建查询解析器实例对象20

21 * 第一个参数:默认搜索的域。22

23 * 如果在搜索的时候,没有特别指定搜索的域,则按照默认的域进行搜索24

25 * 如何在搜索的时候指定搜索域呢?26

27 * 答:格式 域名:关键词 即 name:java教程28

29 *30

31 * 第二个参数:分词器 ,对关键词做分词处理32

33     */

34

35     QueryParser parser = new QueryParser("description", analyzer);36

37     //设置组合条件查询38

39     //Query query = queryParser.parse("name:java教程 or description:apache");40

41     //Query query = queryParser.parse("name:java教程 or apache");

42

43     Query query = parser.parse("name:java教程");44

45 doSearch(query);46

47   } catch(Exception e) {48

49 e.printStackTrace();50

51 }52

53 }

/**

* 查询解析器查询  之  QueryParser查询

*/

@Test

public void queryByQueryParser(){

try {

//1、加载分词器

Analyzer analyzer = new StandardAnalyzer();

/**

* 2、创建查询解析器实例对象

* 第一个参数:默认搜索的域。

*          如果在搜索的时候,没有特别指定搜索的域,则按照默认的域进行搜索

*          如何在搜索的时候指定搜索域呢?

*          答:格式  域名:关键词        即   name:java教程

*

* 第二个参数:分词器   ,对关键词做分词处理

*/

QueryParser parser = new QueryParser("description", analyzer);

// 设置组合条件查询

//      Query query = queryParser.parse("name:java教程 or description:apache");

// Query query = queryParser.parse("name:java教程 or apache");

Query query = parser.parse("name:java教程");

doSearch(query);

} catch (Exception e) {

e.printStackTrace();

}

}

6.3.4 MultiFieldQueryParser

通过MulitFieldQueryParse对多个域查询。

1 /**

2

3 * 查询解析器查询 之 MultiFieldQueryParser查询4

5 *6

7 * 特点:同时指定多个搜索域,并且对关键做分词处理8

9 */

10

11 @Test12

13 public voidqueryByMultiFieldQueryParser(){14

15   try{16

17     //1、定义多个搜索的 name、description

18

19     String[] fields = {"name","description"};20

21     //2、加载分词器

22

23     Analyzer analyzer = newStandardAnalyzer();24

25     //3、创建 MultiFieldQueryParser实例对象

26

27     MultiFieldQueryParser mParser = newMultiFieldQueryParser(fields, analyzer);28

29     Query query = mParser.parse("lucene教程");30

31 doSearch(query);32

33   } catch(Exception e) {34

35 e.printStackTrace();36

37 }38

39 }

/**

* 查询解析器查询  之  MultiFieldQueryParser查询

*

*     特点:同时指定多个搜索域,并且对关键做分词处理

*/

@Test

public void queryByMultiFieldQueryParser(){

try {

//1、定义多个搜索的  name、description

String[] fields = {"name","description"};

//2、加载分词器

Analyzer analyzer = new StandardAnalyzer();

//3、创建 MultiFieldQueryParser实例对象

MultiFieldQueryParser mParser = new MultiFieldQueryParser(fields, analyzer);

Query query = mParser.parse("lucene教程");

doSearch(query);

} catch (Exception e) {

e.printStackTrace();

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值