ITKeyword,专注技术干货聚合推荐

注册 | 登录

JAVA_WEB项目之Lucene检索框架中的IndexWriter、IndexSearch优化

chenchudongsg 分享于 2014-07-24

推荐:JAVA_WEB项目之Lucene检索框架实现增删查改的代码优化以及分页功能实现

上一篇:JAVA_WEB项目之Lucene检索框架入门案例初步给出了一个入门的例子,接下来是对上一篇例子的优化和增加删除,修改document,以及通过更新索引库和删除索引

2020腾讯云共同战“疫”,助力复工(优惠前所未有!4核8G,5M带宽 1684元/3年),
地址https://cloud.tencent.com/act/cps/redirect?redirect=1053

2020阿里云最低价产品入口,含代金券(新老用户有优惠),
地址https://www.aliyun.com/minisite/goods

上一篇介绍了:JAVA_WEB项目之Lucene检索框架实现增删查改的代码优化以及分页功能实现,接下来实现对上一篇编写的代码进行抽取优化。还有对于indexWriter要注意的是如果在web程序中有多个用户访问时,我们应该是让整个web程序中只有一个indexWriter,因为我们都知道indexWriter的对应一个索引库,如果我们让每个用户也就是一个线程对应一个indexWriter会出现线程同步问题。因此我们可以在整个项目中只有有一个indexWriter, 它是线程安全,所有的请求都可以使用同一个indexWriter。还有一种值得注意的情况就是:假设我们整个web项目中只有有一个indexSearch时,会出现什么情况。例如:如果我们先通过indexSeach查询数据,接着通过indexWriter向索引库中添加数据,然后在查询数据。我们一般会觉得没有多大的问题,但是问题还是产生了。这种情况的结果是:我们两次查询的结果都一样,中间刚插入的数据并没查询到,这是为什么?下面给出一个解释:这是因为如果我们在web项目中如果只有一个indexSeach的时候,肯定是在web项目初始化的时候哦实例化,因此就会被载入内存,在内存中也就只是存在一个indexSeach,但我们第一次查询的时候没有问题,然后添加数据,这个时候我们在用内存中indexSeach进行第二次查询的时候就会出现问题了,因为indexSeach并没有和索引库中的数据同步,导致我们第一次查询和第二次查询期间引起的数据结果不一样。

下面用例子来介绍:首先还是一个实体类Goods:

package com.shop.demo;

public class Goods {

	private Integer id;
	private String name;
	private Double price;
	private String pic;
	private String remark;
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Double getPrice() {
		return price;
	}
	public void setPrice(Double price) {
		this.price = price;
	}
	public String getPic() {
		return pic;
	}
	public void setPic(String pic) {
		this.pic = pic;
	}
	public String getRemark() {
		return remark;
	}
	public void setRemark(String remark) {
		this.remark = remark;
	}
	
	
}

接着是配置类ConfigureLucene:

package com.shop.demo;

import java.io.File;
import java.io.IOException;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;

public class ConfigureLucene {
	private ConfigureLucene(){}
	//创建索引库
	private static Directory dir=null;
	//创建分词器
	private static Analyzer ana=null;
	static{
		//根据指定的路径创建索引库,如果路径不存在就会创建
		try {
			dir=FSDirectory.open(new File("c:/demo"));
			//不同的分词器的版本不同,分词的算法不同,StandardAnalyzer只适用于英文
			ana=new StandardAnalyzer(Version.LUCENE_30);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			throw new RuntimeException(e);
		}
	}
	public static Directory getDir() {
		return dir;
	}
	public static Analyzer getAna() {
		return ana;
	}
	
}

接着是第一个工具类DocumentUtil:

package com.shop.demo;

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

public class DocumentUtil {
	private DocumentUtil(){}
	/**
	 * 把goods对象转为document对象
	 */
	public static Document goodsToDocument(Goods goods){
		//把goods对象转为document
		Document doc=new Document();
		doc.add(new Field("id", goods.getId().toString(), Store.YES, Index.NOT_ANALYZED));
		doc.add(new Field("name", goods.getName(), Store.YES, Index.ANALYZED));
		doc.add(new Field("price", goods.getPrice().toString(), Store.YES, Index.NOT_ANALYZED));
		doc.add(new Field("remark", goods.getRemark(), Store.NO, Index.ANALYZED));
		return doc;
	}
	
	/**
	 * 把document对象转为goods对象
	 */
	public static Goods documentToGoods(Document doc){
		Goods goods=new Goods();
		goods.setId(Integer.parseInt(doc.get("id")));
		goods.setName(doc.get("name"));
		goods.setPrice(Double.parseDouble(doc.get("price")));
		goods.setRemark(doc.get("remark"));
		return goods;
	}
}

下面是第二个工具类LuceneUtil:

推荐:JAVA_WEB项目之Lucene实现检索结果排序和关键字在索引库中多字段查询结果进行高亮显示 - 陈楚东的专栏-学编程切记心浮气躁,贵在坚持 - 博客频道

上一篇介绍了JAVA_WEB项目之Lucene使用中文分词器IKAnalyzer3.2.8,接下来对上一篇的代码实现排序的效果和关键字在索引库中多字段查询结果进行高亮显示。 首先是

package com.shop.demo;

import java.io.IOException;

import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriter.MaxFieldLength;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.LockObtainFailedException;
import org.wltea.analyzer.lucene.IKAnalyzer;

public class LuceneUtil {
	//项目启动的时候创建,关闭的时候销毁
	private static IndexWriter indexWriter=null;
	private static IndexSearcher indexSearcher=null;
	static{//类加载的时候初始化indexWriter
		try {
			indexWriter=new IndexWriter(ConfigureLucene.getDir(), ConfigureLucene.getAna(), MaxFieldLength.LIMITED);
			//在项目销毁的时候关闭indexWriter,每个应用程序对应一个Runtime
			Runtime.getRuntime().addShutdownHook(new Thread(){
				@Override
				public void run() {
					// TODO Auto-generated method stub
					try {
						System.out.println("--J2SE 资源销毁的代码在此处执行--");
						indexWriter.close();
					} catch (Exception e) {
						// TODO Auto-generated catch block
						throw new RuntimeException(e);
					}
				}
			});
		} catch (Exception e) {
			// TODO Auto-generated catch block
			throw new RuntimeException(e);
		}
	}
	/**
	 * 在用的时候取indexWriter,关闭IndexSearcher,因为我们知道如果不关闭IndexSearcher,下次取得IndexSearcher是从内存中取得,并没有同步到索引库
	 * 因此会导致我们刚插入的数据在用IndexSearcher查询的时候会查询不得到刚插入的数据
	 * @return
	 */
	public static IndexWriter getIndexWriter() {
		closeIndexSearcher();//关键代码
		return indexWriter;
	}
	public static IndexSearcher getIndexSearcher() {
		//作用: 避免其他线程等待,意思是指如果有一个线程执行到创建indexSearcher之后,那么下一个或者多个线程就不用在进入到synchronized里面
		if(indexSearcher==null){
			synchronized (LuceneUtil.class) {//如果没有其他线程创建了indexSearcher,只允许一个线程进入到里面创建
				if(indexSearcher==null){//作用:是否需要创建indexSearcher
					try {
						indexSearcher=new IndexSearcher(ConfigureLucene.getDir());
					} catch (Exception e) {
						// TODO Auto-generated catch block
						throw new RuntimeException(e);
					}
				}
			}
			
		}
		return indexSearcher;
	}
	public static void closeIndexWriter(){
		if(indexWriter!=null){
			try {
				indexWriter.close();
				indexWriter=null;
			} catch (Exception e) {
				// TODO Auto-generated catch block
				throw new RuntimeException(e);
			}
		}
	}
	public static void closeIndexSearcher(){
		if(indexSearcher!=null){
			try {
				indexSearcher.close();
				indexSearcher = null;
			} catch (Exception e) {
				// TODO Auto-generated catch block
				throw new RuntimeException(e);
			}
		}
	}
}

接着是实现类:HelloWordLucene

package com.shop.demo2;

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.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Index;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriter.MaxFieldLength;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.QueryParser;
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;
/**
 *  此案例实现Lucene向索引库中添加索引和查询索引的功能
 * @author Administrator
 *
 */
public class HelloWordLucene {

	/**
	 * 把good商品对象添加到索引库中
	 * @param goods
	 */
	public void addDocument(Goods goods){
		//创建indexWriter
		IndexWriter indexwriter=null;
		try {
			indexwriter=LuceneUtil.getIndexWriter();
			//把goods对象转为document
			//Document doc=new Document();
			/**
			 * Store配置field字段是否存储到索引库
			 * YES:字段存储到索引库中,以后查询的时候可以查询出来
			 * No:不存储到索引库中
			 *  Index: Lucene为提高查询效率,会像字典一样创建索引. 配置此字段是否要建立索引(建立索引的Field就是Term),
			 *  如果建立索引以后就可以通过此字段查询记录
			 *   NOT_ANALYZED: 创建索引,但是Field的不分词(不分开) 整体作为一个索引
			 *   ANALYZED: 不但要建立索引此Field会被分词(可能一个Field分为多个Term的情况)
			 *   NO: 不建立索引,以后不能通过此字段查询数据 
			 *  Store yes Index: ANALYZED: 此Field可以存储,而且Field 关键字支持分词
			 *  Store yes Index: NOT_ANALYZED 此Field可以存储,但是Field不支持分词,作为一个完成Term   例如: 数字 id  price  和URL 专业词汇
			 *  Store yes Index: NO:  可以查询出此字段, 但是此字段不作为查询关键字
			 *  Store no  Index: ANALYZED:  此Field不存储,但是此Field可以做为关键字搜索  
			 *  Store no  Index: NOT_ANALYZED: 此Field不存储,但是此Field可以做为整体(不拆分)关键字搜索
			 *  Store no  Index: NO:  既不建索引也不存储 没有任何意义,如果这样配置则会抛出异常
			 */
//			doc.add(new Field("id", goods.getId().toString(), Store.YES, Index.NOT_ANALYZED));
//			doc.add(new Field("name", goods.getName(), Store.YES, Index.ANALYZED));
//			doc.add(new Field("price", goods.getPrice().toString(), Store.YES, Index.NOT_ANALYZED));
//			doc.add(new Field("remark", goods.getRemark(), Store.NO, Index.ANALYZED));
			indexwriter.addDocument(DocumentUtil.goodsToDocument(goods));
			// 如果没有提交,在没有异常的情况close()之前会自动提交
			indexwriter.commit();
		} catch (Exception e) {
			try {
				indexwriter.rollback();
				throw new RuntimeException(e);
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				throw new RuntimeException(e);
			}
		}
	}
	/**
	 * 根据指定的条件查询,
	 * @param name 指定的关键字
	 * @return 封装了goods对象的list集合
	 */
	public List<Goods> queryGoods(String name){
		List<Goods> goodsList=new ArrayList<Goods>();
		//创建查询对象
		IndexSearcher searcher=null;
		//创建分词器
		Analyzer analyzer=null;
		try {
			searcher=LuceneUtil.getIndexSearcher();
			// 创建分词器,给查询的关键字先做分词操作,然后在到索引库中匹配Term
			analyzer=new StandardAnalyzer(Version.LUCENE_30);
			//创建查询解析对象,"name"指定从索引库中的哪个field属性里面查找,也就是name到那个Term(key value)中去查询
			QueryParser queryParser=new QueryParser(Version.LUCENE_30, "name", analyzer);
			//  指定查询的关键字到索引库查询
			Query query=queryParser.parse(name);
			/**
			 * 根据给定的关键字查询,与索引库Term去匹配,5代表: 期望返回的结果数
			 *  第一次查询: indexSearcher.search 只能获取文档的索引号和匹配的数量
			 *  返回的结果是TopDoc类型
			 *  totalHits: 命中数, 数组的长度,后面用来做分页
			 *  ScoreDoc[]: 存储匹配的文档编号的数组
			 *  Score: 文档的积分,按照命中率自动算出来
			 *  Doc:当前文档的编号
			 */
			TopDocs topDocs= searcher.search(query, 5);
			// 此变量/每页显示的记录数就是总页数
			System.out.println("真正命中的结果数:" + topDocs.totalHits);
			// 返回的是符合条件的文档编号,并不是文档本事
			ScoreDoc scoreDocs[]= topDocs.scoreDocs;
			for(int i=0;i<scoreDocs.length;i++){
				ScoreDoc scoreDoc= scoreDocs[i];
				System.out.println("真正的命中率:"+scoreDoc.score);
				System.out.println("存储的是文档编号:"+scoreDoc.doc);
				Document doc= searcher.doc(scoreDoc.doc);
				System.out.println(doc.get("id"));
				System.out.println(doc.get("name"));
				System.out.println(doc.get("price"));
				System.out.println(doc.get("remark"));
				System.out.println("---------");
//				Goods goods=new Goods();
//				goods.setId(Integer.parseInt(doc.get("id")));
//				goods.setName(doc.get("name"));
//				goods.setPrice(Double.parseDouble(doc.get("price")));
//				goods.setRemark(doc.get("remark"));
				goodsList.add(DocumentUtil.documentToGoods(doc));
			}
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			throw new RuntimeException(e);
		} 
		return goodsList;
	}
	
	public void deleteDocument(int id){
		//创建indexWriter
		IndexWriter indexwriter=null;
		try {
			indexwriter=LuceneUtil.getIndexWriter();
			// 一般来说都是通过id来删除,所以即使是通过name查询,ID也要建索引,因为更新和删除需要id
			// 根据ID把符合条件的document对象删除掉,但是索引(term) 并没有删除
			indexwriter.deleteDocuments(new Term("id", id+""));
			//同步删除索引库中的索引部分
			indexwriter.optimize();
			// 如果没有提交,在没有异常的情况close()之前会自动提交
			indexwriter.commit();
		} catch (Exception e) {
			try {
				indexwriter.rollback();
				throw new RuntimeException(e);
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				throw new RuntimeException(e);
			}
		}
	}
	public void updateDocument(Goods goods){
		IndexWriter indexwriter=null;
		try{
			indexwriter=LuceneUtil.getIndexWriter();
			indexwriter.updateDocument(new Term("id", goods.getId().toString()), DocumentUtil.goodsToDocument(goods));
			indexwriter.optimize();
			indexwriter.commit();
		}catch (Exception e) {
			// TODO: handle exception
			try {
				indexwriter.rollback();
				throw new RuntimeException(e);
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				throw new RuntimeException(e);
			}
			
		}
	}
	/**
	 * 实现分页的功能  
	 * @param name 查询的关键字
	 * @param currentPage 当前的页数
	 * @return 记录数
	 */
	public List<Goods> queryByPage(String name,int currentPage){
		int number=5; // 每页显示5条
		List<Goods> goodsList=new ArrayList<Goods>();
		IndexSearcher indexSearcher=null;
		Analyzer analyzer=null;
		try {
			// 创建查询对象
			indexSearcher=LuceneUtil.getIndexSearcher();
			// 创建分词器,给查询的关键字先做分词操作,然后在到索引库中匹配Term
			analyzer=new StandardAnalyzer(Version.LUCENE_30);
			// 查询数据的解析器, name到那个Term(key value)中去查询
			QueryParser parser=new QueryParser(Version.LUCENE_30,"name",analyzer);
			// 指定查询的关键字
			Query query=parser.parse(name);
			TopDocs topDocs=indexSearcher.search(query,currentPage*number);
			// 此变量/每页显示的记录数就是总页数
			System.out.println("真正命中的结果数:" + topDocs.totalHits);
			int totalPage=0;
			if(topDocs.totalHits%number!=0){
				totalPage=topDocs.totalHits/number+1;
			}else{
				totalPage=topDocs.totalHits/number;
			}
			System.out.println("通过系统的总结果数/每页显示的数量=总页数" + totalPage);
			// 返回的是符合条件的文档编号,并不是文档本事
			ScoreDoc[] scoreDocs = topDocs.scoreDocs;
			// 去期望值  和实际值的 最小值
			System.out.println("真正查询出来的数组的长度:" + scoreDocs.length);
			for(int i=(currentPage-1)*number;i<scoreDocs.length;i++){
				ScoreDoc scoreDoc=scoreDocs[i];
				System.out.println("存储了命中率积分:" + scoreDoc.score);
				System.out.println("存储的是文档编号:" + scoreDoc.doc);
				// 第二次查询: 通过文档的编号,查询真正的文档信息
				Document document=indexSearcher.doc(scoreDoc.doc);
				goodsList.add(DocumentUtil.documentToGoods(document));
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return goodsList;
	}
	
}


最后是测试类:

package com.shop.demo;

import java.util.List;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class HelloWordLuceneTest {

	private static HelloWordLucene hellowod;
	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		hellowod=new HelloWordLucene();
	}

	@AfterClass
	public static void tearDownAfterClass() throws Exception {
		hellowod=null;
	}

	@Test
	public void testAddDocument() {
		Goods goods=new Goods();
		goods.setId(11);
		goods.setName("IBM Computer12 ");
		goods.setPrice(2333.9);
		goods.setRemark("IBM Computer is good");
		hellowod.addDocument(goods);
	}


	@Test
	public void testquery() {
		List<Goods> list= hellowod.queryGoods("ibm");
		for(Goods good:list){
			System.out.println("商品编号:"+good.getId()+",商品名称:"+good.getName()+
					",商品价格:"+good.getPrice()+",商品的详细信息:"+good.getRemark()
					);
		}
	}
	
	@Test
	public void testdelete() {
		 hellowod.deleteDocument(12);
	}
	
	@Test
	public void testupdate() {
		Goods goods=new Goods();
		goods.setId(12);
		goods.setName("HP Com");
		goods.setPrice(3000.0);
		goods.setRemark("HP very goods");
		hellowod.updateDocument(goods);
	}
	
	@Test
	public void testqueryByPage() {
		List<Goods> list= hellowod.queryByPage("ibm", 1);
		for(Goods good:list){
			System.out.println("商品编号:"+good.getId()+",商品名称:"+good.getName()+
					",商品价格:"+good.getPrice()+",商品的详细信息:"+good.getRemark()
					);
		}
	}
	/**
	 * 先查询,在插入,然后在查询 
	 */
	@Test
	public void test9() {
		hellowod.queryGoods("ibm");
		System.out.println("----------------->");
		Goods goods=new Goods();
		goods.setId(12);
		goods.setName("IBM Computer12 ");
		goods.setPrice(2333.9);
		goods.setRemark("IBM Computer is good");
		hellowod.addDocument(goods);
		System.out.println("----------------->");
		hellowod.queryGoods("ibm");
	}
	
	@Test
	public void test8() {
		
	}
}




推荐:JAVA_WEB项目之使用Spring的xml配置方式在项目中管理Lucene检索框架

Lucene检索在web项目开发中也许会成为一个需求,我们可以把Lucene单独我作为插件到项目中的单独的包下进行开发和管理: 下面是个人学习项目开发中写的代码,也许

上一篇介绍了:JAVA_WEB项目之Lucene检索框架实现增删查改的代码优化以及分页功能实现,接下来实现对上一篇编写的代码进行抽取优化。还有对于indexWriter要注意的是如果在web程序中有多个用户访问

相关阅读排行


相关内容推荐

最新文章

×

×

请激活账号

为了能正常使用评论、编辑功能及以后陆续为用户提供的其他产品,请激活账号。

您的注册邮箱: 修改

重新发送激活邮件 进入我的邮箱

如果您没有收到激活邮件,请注意检查垃圾箱。