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

注册 | 登录

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

chenchudongsg 分享于 2014-07-25

2019阿里云全部产品优惠券(新购或升级都可以使用,强烈推荐)
领取地址https://promotion.aliyun.com/ntms/yunparter/invite.html

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


下面是个人学习项目开发中写的代码,也许并没有贴出全部的代码,贴出的只是其中核心的代码,但是没有贴出的代码我也在前几篇博文中已经涉及到:具体看左边分类栏目:JAVA_WEB项目系列

下面简单的贴出使用弹簧的XML配置方式管理的Lucene在项目中的使用:

首先在SRC目录下配置的applicationContext-lucene.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans
	xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:context="http://www.springframework.org/schema/context" 
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
	">
	<bean id="configureLucene" class="com.shop.lucene.ConfigureLucene" init-method="init"></bean>
	<bean id="documentUtil" class="com.shop.lucene.DocumentUtil"></bean>
	<bean id="luceneUtil" class="com.shop.lucene.LuceneUtil" init-method="init" destroy-method="destory">
		<property name="configureLucene" ref="configureLucene"></property>
	</bean>
	<bean id="highlighterUtil" class="com.shop.lucene.HighlighterUtil" init-method="init">
		<property name="configureLucene" ref="configureLucene"></property>
	</bean>
	<bean id="luceneService" class="com.shop.lucene.LuceneServiceImpl">
		<property name="documentUtil" ref="documentUtil"></property>
		<property name="luceneUtil" ref="luceneUtil"></property>
		<property name="highlighterUtil" ref="highlighterUtil"></property>
	</bean></beans>



接下来是实体类商品:

package com.shop.pojo;

import java.sql.Timestamp;

/**
 * Goods entity. @author MyEclipse Persistence Tools
 */

public class Goods implements java.io.Serializable {

	// Fields

	private Integer id;
	private String name;
	private Double price;
	private String pic;
	private String remark;
	private String xremark;
	private Timestamp date;
	private Boolean commend;
	private Boolean open;
	private Category category;

	// Constructors

	/** default constructor */
	public Goods() {
	}

	/** minimal constructor */
	public Goods(Timestamp date) {
		this.date = date;
	}


	// Property accessors

	public Integer getId() {
		return this.id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return this.name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Double getPrice() {
		return this.price;
	}

	public void setPrice(Double price) {
		this.price = price;
	}

	public String getPic() {
		return this.pic;
	}

	public void setPic(String pic) {
		this.pic = pic;
	}

	public String getRemark() {
		return this.remark;
	}

	public void setRemark(String remark) {
		this.remark = remark;
	}

	public String getXremark() {
		return this.xremark;
	}

	public void setXremark(String xremark) {
		this.xremark = xremark;
	}

	public Timestamp getDate() {
		return this.date;
	}

	public void setDate(Timestamp date) {
		this.date = date;
	}

	public Boolean getCommend() {
		return this.commend;
	}

	public void setCommend(Boolean commend) {
		this.commend = commend;
	}

	public Boolean getOpen() {
		return this.open;
	}

	public void setOpen(Boolean open) {
		this.open = open;
	}

	public Category getCategory() {
		return category;
	}

	public void setCategory(Category category) {
		this.category = category;
	}


}


映射文件就不贴出。

配置类:ConfigureLucene类

package com.shop.lucene;

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;
import org.wltea.analyzer.lucene.IKAnalyzer;

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


高亮的 工具类:HighlighterUtil

package com.shop.lucene;

import java.io.IOException;

import org.apache.lucene.search.Query;
import org.apache.lucene.search.highlight.Formatter;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.Scorer;
import org.apache.lucene.search.highlight.SimpleFragmenter;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;

public class HighlighterUtil {

	private Formatter formatter=null;
	private ConfigureLucene configureLucene=null;
	public void setConfigureLucene(ConfigureLucene configureLucene) {
		this.configureLucene = configureLucene;
	}
	public void init(){
		formatter=new SimpleHTMLFormatter("<font color='red'>", "</font>");
	}
	public  String setHighlighterText(Query query,String iniData,int length){
		String result=null;
		try {
			// query对象中有查询的关键字,字段匹配关键字的内容将会高亮
			Scorer scorer=new QueryScorer(query);
			// 实现高亮的工具类
			Highlighter highlighter=new Highlighter(formatter, scorer);
			// 设置高亮后的字符长度
			highlighter.setTextFragmenter(new SimpleFragmenter(length));
			//给指定字段进行高亮效果
			result= highlighter.getBestFragment(configureLucene.getAna(), null,iniData);
			
			if(result==null){
				if(iniData.length()>length){
					result=iniData.substring(0, length);
				}else{
					result=iniData;
				}
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return result;
	}
}


工具类:LuceneUtil

package com.shop.lucene;

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  IndexWriter indexWriter=null;
	private  IndexSearcher indexSearcher=null;
	private ConfigureLucene configureLucene=null;
	public void setConfigureLucene(ConfigureLucene configureLucene) {
		this.configureLucene = configureLucene;
	}
	
   public void init(){
		try {
			indexWriter=new IndexWriter(configureLucene.getDir(), configureLucene.getAna(), MaxFieldLength.LIMITED);
			//在项目销毁的时候关闭indexWriter,每个应用程序对应一个Runtime
		} catch (Exception e) {
			// TODO Auto-generated catch block
			throw new RuntimeException(e);
		}
	}
   public void destory(){
	   try {
			System.out.println("--资源销毁的代码在此处执行--");
			indexWriter.close();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			throw new RuntimeException(e);
		}
   }
	/**
	 * 在用的时候取indexWriter,关闭IndexSearcher,因为我们知道如果不关闭IndexSearcher,下次取得IndexSearcher是从内存中取得,并没有同步到索引库
	 * 因此会导致我们刚插入的数据在用IndexSearcher查询的时候会查询不得到刚插入的数据
	 * @return
	 */
	public   IndexWriter getIndexWriter() {
		closeIndexSearcher();//关键代码
		return indexWriter;
	}
	public   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   void closeIndexWriter(){
		if(indexWriter!=null){
			try {
				indexWriter.close();
				indexWriter=null;
			} catch (Exception e) {
				// TODO Auto-generated catch block
				throw new RuntimeException(e);
			}
		}
	}
	public  void closeIndexSearcher(){
		if(indexSearcher!=null){
			try {
				indexSearcher.close();
				indexSearcher = null;
			} catch (Exception e) {
				// TODO Auto-generated catch block
				throw new RuntimeException(e);
			}
		}
	}
}

工具类DocumentUtil:

package com.shop.lucene;

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 com.shop.pojo.Goods;

public class DocumentUtil {
	/**
	 * 把goods对象转为document对象
	 */
	public  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.YES, Index.ANALYZED));
		return doc;
	}
	
	/**
	 * 把document对象转为goods对象
	 */
	public  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;
	}
}



实现类:LuceneServiceImpl

package com.shop.lucene;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TopDocs;
import org.aspectj.lang.JoinPoint;
import org.wltea.analyzer.lucene.IKQueryParser;

import com.shop.pojo.Goods;
/**
 *  此案例实现Lucene向索引库中添加索引和查询索引的功能
 * @author Administrator
 *
 */
public class LuceneServiceImpl implements LuceneService {
	/**
	 *  Aop: 面向切面编程  
	 * 切面,目标对象,切入点,切面表达式,通知(前置 后置 异常 环绕...)
	 *  LuceneServiceImpl 切面  此类的方式运行时动态切入就是通知  例如: addDocument(Goods goods)
	 *   被切的类: GoodsServiceImpl就是目标对象    public void save(Goods t)就是连接点(切入点)
	 */
	private LuceneUtil luceneUtil=null;
	private HighlighterUtil highlighterUtil=null;
	private DocumentUtil documentUtil=null;
	public void setLuceneUtil(LuceneUtil luceneUtil) {
		this.luceneUtil = luceneUtil;
	}
	public void setHighlighterUtil(HighlighterUtil highlighterUtil) {
		this.highlighterUtil = highlighterUtil;
	}
	public void setDocumentUtil(DocumentUtil documentUtil) {
		this.documentUtil = documentUtil;
	}
	/**
	 * 为了方便测试和解耦,我们添加一个包装方法
	 */
	public void addDocumentProxy(JoinPoint joinPoint){
		System.out.println("获取目标对象:"+joinPoint.getTarget());
		System.out.println("获取当前连接点的方法信息:"+joinPoint.getSignature());
		System.out.println("获取当前连接点的参数信息:"+joinPoint.getArgs()[0]);
		this.addDocument((Goods)joinPoint.getArgs()[0]);
	}
	/* (non-Javadoc)
	 * @see com.shop.lucene.LuceneService#addDocument(com.shop.pojo.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);
			}
		}
	}
	/* (non-Javadoc)
	 * @see com.shop.lucene.LuceneService#queryGoods(java.lang.String)
	 */
	public List<Goods> queryGoods(String keyword){
		List<Goods> goodsList=new ArrayList<Goods>();
		//创建查询对象
		IndexSearcher searcher=null;
		try {
			searcher=luceneUtil.getIndexSearcher();
			//  指定查询的关键字到索引库查询
			Query query=IKQueryParser.parseMultiField(new String[]{"name","remark"}, keyword);
			/**
			 * 根据给定的关键字查询,与索引库Term去匹配,5代表: 期望返回的结果数
			 *  第一次查询: indexSearcher.search 只能获取文档的索引号和匹配的数量
			 *  返回的结果是TopDoc类型
			 *  totalHits: 命中数, 数组的长度,后面用来做分页
			 *  ScoreDoc[]: 存储匹配的文档编号的数组
			 *  Score: 文档的积分,按照命中率自动算出来
			 *  Doc:当前文档的编号
			 */
			//增加排序的效果
			/*对象查询的结果进行排序: Lucene排序有两种: 命中率排序, 根据字段排序
			 *注意 这两种排序方式不互斥的,如果选择按字段排序命中率是不会被计算出来, 但是字段排序本身可以支持多字段
			 *被排序的字段,和被删除更新一样 字段必须创建索引
			 * true代表的是降序
			 */
			Sort sort=new Sort(new SortField("id",SortField.INT, true),new SortField("price", SortField.DOUBLE,true));
			TopDocs topDocs= searcher.search(query, null, 5, sort);
			// 此变量/每页显示的记录数就是总页数
			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"));
				doc.getField("name").setValue(highlighterUtil.setHighlighterText(query, doc.get("name"), 10));
				doc.getField("remark").setValue(highlighterUtil.setHighlighterText(query, doc.get("remark"), 15));
				goodsList.add(documentUtil.documentToGoods(doc));
			}
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			throw new RuntimeException(e);
		} 
		return goodsList;
	}
	
	/* (non-Javadoc)
	 * @see com.shop.lucene.LuceneService#deleteDocument(int)
	 */
	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);
			}
		}
	}
	/* (non-Javadoc)
	 * @see com.shop.lucene.LuceneService#updateDocument(com.shop.pojo.Goods)
	 */
	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);
			}
			
		}
	}
	/* (non-Javadoc)
	 * @see com.shop.lucene.LuceneService#queryByPage(java.lang.String, int)
	 */
	public List<Goods> queryByPage(String name,int currentPage){
		int number=5; // 每页显示5条
		List<Goods> goodsList=new ArrayList<Goods>();
		IndexSearcher indexSearcher=null;
		try {
			// 创建查询对象
			indexSearcher=luceneUtil.getIndexSearcher();
			// 指定查询的关键字
			Query query=IKQueryParser.parse("name", 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.service.impl;

import java.util.List;

import javax.annotation.Resource;

import org.hibernate.Session;
import org.springframework.stereotype.Service;

import com.shop.lucene.LuceneService;
进口com.shop.pojo.Goods;
import com.shop.service.GoodsService;

@Service("goodsService")
public class GoodsServiceImpl extends BaseServiceImpl<Goods> implements GoodsService{
	@Resource
	private LuceneService luceneService;
	public List<Goods> queryGoodsByCommendAndOpenAndCid(int cid) {
		String hql="From Goods g Left join Fetch g.category where g.commend=true and g.open=true and g.category.id=:cid order by g.date";
		Session session= sessionFactory.getCurrentSession();
		return session.createQuery(hql).setInteger("cid", cid).setFirstResult(0).setMaxResults(3).list();
	}

	@Override
	public void save(Goods t) {
		// TODO Auto-generated method stub
		super.save(t);
		//把商品同步添加到索引库中
		luceneService.addDocument(t);
	}

	
}


结果:




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

相关阅读排行


用户评论

游客

相关内容推荐

最新文章

×

×

请激活账号

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

您的注册邮箱: 修改

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

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