Springboot集成lucene4.7

Lucene是一套用于全文检索和搜寻的开源程式库,一星期的研究实现了简单的基于cms新闻管理系统的全文搜索引擎,自己做笔记记录一下,个人理解可以把lucene当做一个文档型数据库:

首先先了解lucene:

  百科是这样说的:Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,即它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。

lucene常用类

 (1)IndexWriter 索引过程的核心组件。这个类负责创建新索引或者打开已有索引,以及向索引中添加、删除或更新索引文档的信息。可以把IndexWriter看做这样一个对象:提供针对索引文件的写入操作,但不能用于读取或搜索索引。IndexWriter需要开辟一定空间来存储索引,该功能可以由Directory完成。

 (2)Diretory索引存放的位置,它是一个抽象类,它的子类负责具体制定索引的存储路径。lucene提供了两种索引存放的位置,一种是磁盘,一种是内存。一般情况将索引放在磁盘上;相应地lucene提供了FSDirectory和RAMDirectory两个类。

(3)Analyzer 分析器,主要用于分析搜索引擎遇到的各种文本,Analyzer的工作是一个复杂的过程:把一个字符串按某种规则划分成一个个词语,并去除其中的无效词语(停用词),这里说的无效词语如英文中的“of”、“the”,中文中的“的”、“地”等词语,这些词语在文章中大量出现,但是本身不包含什么关键信息,去掉有利于缩小索引文件、提高效率、提高命中率。分词的规则千变万化,但目的只有一个:按语义划分。这点在英文中比较容易实现,因为英文本身就是以单词为单位的,已经用空格分开;而中文则必须以某种方法将连成一片的句子划分成一个个词语。具体划分方法下面再详细介绍,这里只需了解分析器的概念即可。

// 定义分词器

// Analyzer analyzer = new IKAnalyzer();//分词器

//Analyzer analyzer = new SimpleAnalyzer(Version.LUCENE_47);// 简单分词器

// Analyzer analyzer3 = new CJKAnalyzer(VERSION);// 二元切分

Analyzer analyzer = new IKAnalyzer(true);// 语意分词  false关闭智能分词 (对分词的精度影响较大)

//Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_47);// 创建标准分词器

   (4)Document 文档 Document相当于一个要进行索引的单元,可以是文本文件、字符串或者数据库表的一条记录等等,一条记录经过索引之后,就是以一个Document的形式存储在索引文件,索引的文件都必须转化为Document对象才能进行索引。

     Document doc = new Document();

       Field content = null;

        Field title = null;

        Field createTime = null;

        Field type = null;

        doc.add(id);

        doc.add(content);

        doc.add(title);

        doc.add(createTime);

        doc.add(type);

(5)Field 一个Document可以包含多个信息域,比如一篇文章可以包含“标题”、“正文”等信息域,这些信息域就是通过Field在Document中存储的。选择field还是比较重要的:

是否分词(Tokenized)

是:对该field存储的内容进行分词,分词的目的,就是为了索引。

  比如:商品名称、商品描述、商品价格

否:不需要对field存储的内容进行分词,不分词,不代表不索引,而是将整个内容进行索引。

  比如:商品id

是否索引(Indexed)

是:将分好的词进行索引,索引的目的,就是为了搜索。

  比如:商品名称、商品描述、商品价格、商品id

否:不索引,也就是不对该field域进行搜索。

是否存储(Stored)

是:将field域中的内容存储到文档域中。存储的目的,就是为了搜索页面显示取值用的。

  比如:商品名称、商品价格、商品id、商品图片地址

否:不将field域中的内容存储到文档域中。不存储,则搜索页面中没法获取该field域的值。

  比如:商品描述,由于商品描述在搜索页面中不需要显示,再加上商品描述的内容比较多,所以就不需要进行存储。如果需要商品描述,则根据搜索出的商品ID去数据库中查询,然后显示出商品描述信息即可。

  根据开发需求选择不同的Filed类型:我找到的常用类型

(6)IndexSearcher 是lucene中最基本的检索工具,所有的检索都会用到IndexSearcher工具。

 (7)IndexReader打开一个Directory读取索引类。

  (8)Query 查询,抽象类,必须通过一系列子类来表述检索的具体需求,lucene中支持模糊查询,语义查询,短语查询,组合查询等等,如有TermQuery,BooleanQuery,RangeQuery,WildcardQuery等一些类。

这个也是比较重要的:查询的条件查询:

BooleanClause用于表示布尔查询子句关系的类,包括:BooleanClause.Occur.MUST,BooleanClause.Occur.MUST_NOT,BooleanClause.Occur.SHOULD。有以下6种组合:

1.MUST和MUST:取得连个查询子句的交集。

2.MUST和MUST_NOT:表示查询结果中不能包含MUST_NOT所对应得查询子句的检索结果。

3.MUST_NOT和MUST_NOT:无意义,检索无结果。

4.SHOULD与MUST、SHOULD与MUST_NOT:SHOULD与MUST连用时,无意义,结果为MUST子句的检索结果。与MUST_NOT连用时,功能同MUST。

5.SHOULD与SHOULD:意思是 or  表示“或”关系,最终检索结果为所有检索子句的并集。

对于多条件查询还有其他的一些实现类比如 MultiFieldQueryParser.parse()来创建一个Query有多个参数,比较简单查下api就能明白。

(9)QueryParser 解析用户的查询字符串进行搜索,是一个解析用户输入的工具,可以通过扫描用户输入的字符串,生成Query对象。

  (10)TopDocs 根据关键字搜索整个索引库,然后对所有结果进行排序,取指定条目的结果。

  (11)TokenStream Token分词器Analyzer通过对文本的分析来建立TokenStreams(分词数据流)。TokenStream是由一个个Token(分词组成的数据流)。所以说Analyzer就代表着一个从文本数据中抽取索引词(Term)的一种策略。

 (12)AttributeSourceTokenStream即是从Document的域(field)中或者查询条件中抽取一个个分词而组成的一个数据流。TokenSteam中是一个个的分词,而每个分词又是由一个个的属性(Attribute)组成。对于所有的分词来说,每个属性只有一个实例。这些属性都保存在AttributeSource中,而AttributeSource正是TokenStream的父类。

1.依赖注入:

com.janeluo

ikanalyzer

2012_u6

org.apache.lucene

lucene-highlighter

4.7.2

2.实现的增删改查:

package com.yunqi.cms.common;

import java.io.File;

import java.io.IOException;

import java.util.ArrayList;

import java.util.List;

import org.apache.lucene.analysis.Analyzer;

import org.apache.lucene.document.Document;

import org.apache.lucene.document.Field;

import org.apache.lucene.document.Field.Store;

import org.apache.lucene.document.StringField;

import org.apache.lucene.document.TextField;

import org.apache.lucene.index.DirectoryReader;

import org.apache.lucene.index.IndexWriter;

import org.apache.lucene.index.IndexWriterConfig;

import org.apache.lucene.queryparser.classic.QueryParser;

import org.apache.lucene.search.BooleanClause.Occur;

import org.apache.lucene.search.BooleanQuery;

import org.apache.lucene.search.IndexSearcher;

import org.apache.lucene.search.Query;

import org.apache.lucene.search.ScoreDoc;

import org.apache.lucene.search.TopDocs;

import org.apache.lucene.store.Directory;

import org.apache.lucene.store.FSDirectory;

import org.apache.lucene.util.Version;

import org.wltea.analyzer.lucene.IKAnalyzer;

import com.yunqi.cms.entity.Content;

import com.yunqi.cms.vo.ContentVO;

import com.yunqi.cms.vo.LuceneContentVO;

import com.yunqi.platform.common.page.PageList;

import com.yunqi.platform.utils.LocalDateTimeUtils;

import com.yunqi.platform.utils.SystemConfigure;

/**

*

* @ClassName: LuceneUtils 

* @Description: 全文检索

* @author yangqq

* @date 2019年6月15日 上午11:45:00 

* @version V1.0 

* @Copyright: 2019 www.yunqi.com Inc. All rights reserved.

*/

public class LuceneUtils {

// 定义分词器

// Analyzer analyzer = new IKAnalyzer();//分词器

//Analyzer analyzer = new SimpleAnalyzer(Version.LUCENE_47);// 简单分词器

// Analyzer analyzer3 = new CJKAnalyzer(VERSION);// 二元切分

Analyzer analyzer = new IKAnalyzer(true);// 语意分词  false关闭智能分词 (对分词的精度影响较大)

//Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_47);// 创建一个分词器

public String createIndex(Object t) throws IOException {

if (t == null) {

throw new RuntimeException("所传对象不可为空");

}

LuceneContentVO contentvo = new LuceneContentVO();

if (t instanceof LuceneContentVO) {

contentvo = (LuceneContentVO) t;

// 创建Document对象

Document doc = new Document();

// 获取每列数据

if (contentvo.getId() == null) {

throw new RuntimeException("新闻Id不可为空");

}

if (contentvo.getSiteId() == null) {

throw new RuntimeException("站点信息不存在");

}

if(contentvo.getTitle()==null){

throw new RuntimeException("所存内容名称不可为空");

}

if(contentvo.getType()==null){

throw new RuntimeException("文章所属类型不可为空");

}

Field content = null;

Field title = null;

Field createTime = null;

Field type = null;

        Field id = new StringField("id", contentvo.getId().toString(),Store.YES);//标题 StringField索引存储不分词 

title = new TextField("title",  contentvo.getTitle(), Store.YES);


//如果是普通新闻,Content内容不存储,title标题进行分词存储,医生内容的title不分词

        if(contentvo.getType().equals(CmsCommon.CMS_NEWS)){

content = new TextField("content", "",Store.YES);//内容 TextField索引存储分词 

        }else{

        if (contentvo.getContent() != null) {

content = new TextField("content", contentvo.getContent().toString(),Store.YES); 

} else {

content = new TextField("content", "",Store.YES); 

}

//title = new StringField("title", contentvo.getTitle(), Store.YES);

        }


if (contentvo.getCreateTime() != null) {

createTime = new StringField("createTime", LocalDateTimeUtils.formatDateTime(contentvo.getCreateTime()), Store.YES);

} else {

createTime = new StringField("createTime","", Store.YES);

}

type = new StringField("type", contentvo.getType(), Store.YES);

// 添加到Document中

doc.add(id);

doc.add(content);

doc.add(title);

doc.add(createTime);

doc.add(type);

if (contentvo.getSiteId() != null) {

String path = "";

String resUploadPath = SystemConfigure.getValue("res_upload_path");

path = resUploadPath + contentvo.getSiteId() + "/lucence";

// 调用,创建索引库

this.write(doc, path);

} else {

throw new RuntimeException("站点信息不存在");

}

System.out.println("id:" + contentvo.getId().toString());

}

return "成功";

}

// 初始化索引

public void initcreate(List list, Integer siteId) throws IOException {

String path = "";

if (siteId != null) {

// 创建储存路径

String resUploadPath = SystemConfigure.getValue("res_upload_path");

path = resUploadPath + siteId + "/lucence";

delFolder(path);

}

if (list.size() > 0) {

for (LuceneContentVO contentvo : list) {

if (contentvo.getSiteId() == null) {

throw new RuntimeException("站点信息不存在");

}

if (contentvo.getId() == null) {

throw new RuntimeException("新闻Id不可为空");

}

if(contentvo.getTitle()==null){

throw new RuntimeException("所存内容名称不可为空");

}

if(contentvo.getType()==null){

throw new RuntimeException("文章所属类型不可为空");

}

Field content = null;

Field title = null;

Field createTime = null;

Field type = null;

        Field id = new StringField("id", contentvo.getId().toString(),Store.YES);//标题 StringField索引存储不分词 

title = new TextField("title",  contentvo.getTitle(), Store.YES);

//如果是普通新闻,Content内容不存储,title标题进行分词存储,医生内容的title不分词

        if(contentvo.getType().equals(CmsCommon.CMS_NEWS)){

content = new TextField("content", "",Store.YES);//内容 TextField索引存储分词 

        }else{

        if (contentvo.getContent() != null) {

content = new TextField("content", contentvo.getContent().toString(),Store.YES); 

} else {

content = new TextField("content", "",Store.YES); 

}

        }


if (contentvo.getCreateTime() != null) {

createTime = new StringField("createTime", LocalDateTimeUtils.formatDateTime(contentvo.getCreateTime()), Store.YES);

} else {

createTime = new StringField("createTime","", Store.YES);

}

type = new StringField("type", contentvo.getType(), Store.YES);

// 创建Document对象

Document doc = new Document();

// 给title加权

//title.setBoost(4f);

// 添加到Document中

doc.add(id);

doc.add(content);

doc.add(title);

doc.add(createTime);

doc.add(type);

Directory directory = null;

IndexWriterConfig config = null;

IndexWriter iwriter = null;

try {

// 索引库的存储目录()

directory = FSDirectory.open(new File(path));

// 关联当前lucence版本和分值器

config = new IndexWriterConfig(Version.LUCENE_47, analyzer);

// 传入目录和分词器

iwriter = new IndexWriter(directory, config);

iwriter.commit();

// 写入到目录文件中

iwriter.addDocument(doc);

// 提交事务

iwriter.commit();

// 关闭流

iwriter.close();

} catch (IOException e) {

e.printStackTrace();

} finally {

// 关闭流

iwriter.close();

}

}

}

}

// 删除指定文件夹下所有文件

// param path 文件夹完整绝对路径

public static boolean delAllFile(String path) {

boolean flag = false;

File file = new File(path);

if (!file.exists()) {

return flag;

}

if (!file.isDirectory()) {

return flag;

}

String[] tempList = file.list();

File temp = null;

for (int i = 0; i < tempList.length; i++) {

if (path.endsWith(File.separator)) {

temp = new File(path + tempList[i]);

} else {

temp = new File(path + File.separator + tempList[i]);

}

if (temp.isFile()) {

temp.delete();

}

if (temp.isDirectory()) {

delAllFile(path + "/" + tempList[i]);// 先删除文件夹里面的文件

delFolder(path + "/" + tempList[i]);// 再删除空文件夹

flag = true;

}

}

return flag;

}

// 删除文件夹

// param folderPath 文件夹完整绝对路径

public static void delFolder(String folderPath) {

try {

delAllFile(folderPath); // 删除完里面所有内容

String filePath = folderPath;

filePath = filePath.toString();

java.io.File myFilePath = new java.io.File(filePath);

myFilePath.delete(); // 删除空文件夹

} catch (Exception e) {

e.printStackTrace();

}

}

/**

* 封裝一个方法,将关键字词存储到索引文件中

*

* @param doc

* @throws IOException

*/

public void write(Document doc, String dir) throws IOException {

Directory directory = null;

IndexWriterConfig config = null;

IndexWriter iwriter = null;

try {

// 索引库的存储目录

directory = FSDirectory.open(new File(dir));

// 关联当前lucence版本和分值器

config = new IndexWriterConfig(Version.LUCENE_47, analyzer);

// 传入目录和分词器

iwriter = new IndexWriter(directory, config);

iwriter.commit();

// 写入到目录文件中

iwriter.addDocument(doc);

// 提交事务

iwriter.commit();

} catch (IOException e) {

e.printStackTrace();

} finally {

// 关闭流

iwriter.close();

}

}

/**

* 修改

*

* @param Object

*/

public void updateDocument(Object t) throws IOException {

if (t == null) {

throw new RuntimeException("所传对象不可为空");

}

try {

LuceneContentVO contentvo = new LuceneContentVO();

if (t instanceof LuceneContentVO) {

contentvo = (LuceneContentVO) t;

if (contentvo.getId() == null) {

throw new RuntimeException("所传对象ID不可为空");

}

String path = "";

if (contentvo.getSiteId() != null) {

// 创建储存路径

String resUploadPath = SystemConfigure.getValue("res_upload_path");

path = resUploadPath + contentvo.getSiteId() + "/lucence";

}

// 创建Document对象

Document doc = new Document();

if (contentvo.getSiteId() == null) {

throw new RuntimeException("站点信息不存在");

}

if (contentvo.getId() == null) {

throw new RuntimeException("新闻Id不可为空");

}

if(contentvo.getTitle()==null){

throw new RuntimeException("所存内容名称不可为空");

}

if(contentvo.getType()==null){

throw new RuntimeException("文章所属类型不可为空");

}

Field content = null;

Field title = null;

Field createTime = null;

Field type = null;

        Field id = new StringField("id", contentvo.getId().toString(),Store.YES);//标题 StringField索引存储不分词 

title = new TextField("title",  contentvo.getTitle(), Store.YES);

//如果是普通新闻,Content内容不存储,title标题进行分词存储,医生内容的title不分词

        if(contentvo.getType().equals(CmsCommon.CMS_NEWS)){

content = new TextField("content", "",Store.YES);//内容 TextField索引存储分词 

        }else{

        if (contentvo.getContent() != null) {

content = new TextField("content", contentvo.getContent().toString(),Store.YES); 

} else {

content = new TextField("content", "",Store.YES); 

}

//title = new StringField("title", contentvo.getTitle(), Store.YES);

        }


if (contentvo.getCreateTime() != null) {

createTime = new StringField("createTime", LocalDateTimeUtils.formatDateTime(contentvo.getCreateTime()), Store.YES);

} else {

createTime = new StringField("createTime","", Store.YES);

}

type = new StringField("type", contentvo.getType(), Store.YES);

// 添加到Document中

doc.add(id);

doc.add(content);

doc.add(title);

doc.add(createTime);

doc.add(type);

this.deleteDocuments(contentvo.getId(), contentvo.getSiteId(),contentvo.getType());

this.write(doc, path);

System.out.println("contentId:" + contentvo.getId().toString());

}

} catch (Exception e) {

e.printStackTrace();

}

}

// 根据名称和类型分页搜索

public PageList searchPage(Content content) throws IOException {

Directory directory = null;

DirectoryReader ireader = null;

PageList pageList = new PageList<>();

List cList = new ArrayList<>();

if (content == null) {

throw new RuntimeException("查询新闻信息不可为空");

}

int pageNow = content.getCurrentPage();

int pageSize = content.getPageSize();

try {

// 索引库的存储目录

String path = "";

if (content.getSiteId() != null) {

// 创建储存路径

String resUploadPath = SystemConfigure.getValue("res_upload_path");

path = resUploadPath + content.getSiteId() + "/lucence";

}

directory = FSDirectory.open(new File(path));

// 读取索引库的存储目录

ireader = DirectoryReader.open(directory);

// 搜索类

IndexSearcher isearcher = new IndexSearcher(ireader);

//开始

BooleanQuery booleanQuery = new BooleanQuery();

// 条件一内容中必须要有title标题

QueryParser parser1 = new QueryParser(Version.LUCENE_47, "title", analyzer);

// 搜索

Query query1 = parser1.parse(content.getTitle());

booleanQuery.add(query1, Occur.SHOULD);

// 条件二内容中包含的词语

QueryParser parser2 = new QueryParser(Version.LUCENE_47, "content", analyzer);

Query query2 = parser2.parse(content.getTitle());

booleanQuery.add(query2, Occur.SHOULD);

TopDocs topDocs = isearcher.search(booleanQuery, pageSize * pageNow);

System.out.println("查询到的条数\t" + topDocs.totalHits);

ScoreDoc[] scores = topDocs.scoreDocs;

int start = (pageNow - 1) * pageSize;

int end = pageSize * pageNow;

if (topDocs.totalHits <= end) {

for (int i = start; i < topDocs.totalHits; i++) {

Document hitDoc = isearcher.doc(scores[i].doc);

ContentVO co = new ContentVO();

co.setId(Integer.parseInt(hitDoc.get("id")));

co.setContent(hitDoc.get("content"));

co.setTitle(hitDoc.get("title"));

co.setCreateTime(LocalDateTimeUtils.parseDateTime(hitDoc.get("createTime")));

co.setMediaType(hitDoc.get("type"));

cList.add(co);

}

} else {

for (int i = start; i < end; i++) {

Document hitDoc = isearcher.doc(scores[i].doc);

ContentVO co = new ContentVO();

co.setId(Integer.parseInt(hitDoc.get("id")));

co.setContent(hitDoc.get("content"));

co.setTitle(hitDoc.get("title"));

co.setCreateTime(LocalDateTimeUtils.parseDateTime(hitDoc.get("createTime")));

co.setMediaType(hitDoc.get("type"));

cList.add(co);

}

}

pageList.setList(cList);

pageList.setPageSize(content.getPageSize());

pageList.setCurrentPage(content.getCurrentPage());

pageList.setTotalSize(topDocs.totalHits);

return pageList;

} catch (Exception e) {

e.printStackTrace();

} finally {

ireader.close();

directory.close();

}

return pageList;

}

// 根据id和类型不分页搜索

public List searchById(LuceneContentVO content) throws IOException {

Directory directory = null;

DirectoryReader ireader = null;

List cList = new ArrayList<>();

if (content == null) {

throw new RuntimeException("查询新闻信息不可为空");

}

try {

// 索引库的存储目录

String path = "";

if (content.getSiteId() != null) {

// 创建储存路径

String resUploadPath = SystemConfigure.getValue("res_upload_path");

path = resUploadPath + content.getSiteId() + "/lucence";

}

directory = FSDirectory.open(new File(path));

// 读取索引库的存储目录

ireader = DirectoryReader.open(directory);

// 搜索类

IndexSearcher isearcher = new IndexSearcher(ireader);

//开始

BooleanQuery booleanQuery = new BooleanQuery();

// 条件一内容中必须要有id

QueryParser parser1 = new QueryParser(Version.LUCENE_47, "id", analyzer);

// 搜索

Query query1 = parser1.parse(content.getId().toString());

// 条件二内容type属于医生或者普通新闻

QueryParser parser2 = new QueryParser(Version.LUCENE_47, "type", analyzer);

Query query2 = parser2.parse(content.getType());

//Query query2 = parser2.parse("Y");

booleanQuery.add(query1, Occur.MUST);

booleanQuery.add(query2, Occur.MUST);

TopDocs topDocs = isearcher.search(booleanQuery, 1000);

System.out.println("查询到的条数\t" + topDocs.totalHits);

ScoreDoc[] scores = topDocs.scoreDocs;

for (int i = 0; i < topDocs.totalHits; i++) {

Document hitDoc = isearcher.doc(scores[i].doc);

ContentVO co = new ContentVO();

co.setId(Integer.parseInt(hitDoc.get("id")));

co.setContent(hitDoc.get("content"));

co.setTitle(hitDoc.get("title"));

co.setCreateTime(LocalDateTimeUtils.parseDateTime(hitDoc.get("createTime")));

co.setMediaType(hitDoc.get("type"));

cList.add(co);

}

return cList;

} catch (Exception e) {

e.printStackTrace();

} finally {

ireader.close();

directory.close();

}

return cList;

}

/**

* 删除文档

*

* @throws IOException

*/

public void deleteDocuments(Integer id, Integer siteId,String type) throws IOException {

Directory directory = null;

IndexWriterConfig config = null;

IndexWriter iwriter = null;

try {

// 索引库的存储目录

String path = "";

if (siteId != null) {

// 创建储存路径

String resUploadPath = SystemConfigure.getValue("res_upload_path");

path = resUploadPath + siteId + "/lucence";

}

directory = FSDirectory.open(new File(path));

// 关联当前lucence版本和分值器

config = new IndexWriterConfig(Version.LUCENE_47, analyzer);

// 传入目录和分词器

iwriter = new IndexWriter(directory, config);

/*// 删除title中含有关键词“contentId”的文档

iwriter.deleteDocuments(new Term("id", id.toString()));

*/

//开始

BooleanQuery booleanQuery = new BooleanQuery();

// 条件一内容中必须要有id

QueryParser parser1 = new QueryParser(Version.LUCENE_47, "id", analyzer);

// 搜索

Query query1 = parser1.parse(id.toString());

// 条件二内容type属于医生或者普通新闻

QueryParser parser2 = new QueryParser(Version.LUCENE_47, "type", analyzer);

Query query2 = parser2.parse(type);

booleanQuery.add(query1, Occur.MUST);

booleanQuery.add(query2, Occur.MUST);

iwriter.deleteDocuments(booleanQuery);

iwriter.commit();

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

} catch (Exception e) {

e.printStackTrace();

} finally {

iwriter.close();

}

}

}

你可能感兴趣的:(Springboot集成lucene4.7)