JAVA_WEB项目之使用Spring的xml配置方式在项目中管理Lucene检索框架
2014-07-25 15:26
1036 查看
Lucene检索在web项目开发中也许会成为一个需求,我们可以把Lucene单独我作为插件到项目中的单独的包下进行开发和管理:
下面是个人学习项目开发中写的代码,也许并没有贴出全部的代码,贴出的只是其中核心的代码,但是没有贴出的代码我也在前几篇博文中已经涉及到:具体看左边分类栏目:JAVA_WEB项目系列
下面简单的贴出使用弹簧的XML配置方式管理的Lucene在项目中的使用:
首先在SRC目录下配置的applicationContext-lucene.xml:
接下来是实体类商品:
映射文件就不贴出。
配置类:ConfigureLucene类
高亮的 工具类:HighlighterUtil
工具类:LuceneUtil
工具类DocumentUtil:
实现类:LuceneServiceImpl
商品业务逻辑类:
结果:
下面是个人学习项目开发中写的代码,也许并没有贴出全部的代码,贴出的只是其中核心的代码,但是没有贴出的代码我也在前几篇博文中已经涉及到:具体看左边分类栏目: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); } }
结果:
相关文章推荐
- java注解方式(不用xml)配置web框架spring+SpringMVC
- java ssm框架入门(三)正式项目的web.xml配置
- java命令执行jar包(里面的main函数,无web.xml文件)的方式(包括依赖其它的jar包),使用Google-Guava Concurrent包里的Service框架,maven工程
- 项目中使用Spring时配置web.xml的listener侦听接口不能初始化的问题
- JAVA_WEB项目之Lucene检索框架入门案例
- 8 -- 深入使用Spring -- 4...6 AOP代理:基于注解的XML配置文件的管理方式
- JAVA_WEB项目之Spring中使用AOP编程运用到Lucene中实现解耦
- 大型Java项目中使用maven进行管理,pom.xml文件中build的配置
- 如何使用Javaconfig代替web.xml配置spring
- javaWeb项目SpringMVC3.2.1与Mybatis3.0.4整合实例(Mybaits-spring配置文件的几种方式)之一SqlSessionDaoSupport方式
- Java - Spring支持的事务管理类型有哪些?你在项目中使用哪种方式?
- Java框架spring 学习笔记(十八):事务管理(xml配置文件管理)
- JAVA_WEB项目之Lucene检索框架实现增删查改的代码优化以及分页功能实现
- SpringMVC+Spring4+hibernate5使用java类配置spring和springmvc环境(不用配置web.xml)
- JAVA_WEB项目之Lucene检索框架中的IndexWriter、IndexSearch优化
- Java maven项目的搭建,配置web框架以及Spring
- Web项目中使用XML配置加载Spring及Spring MVC的组件
- web.xml中配置spring的几种方式 以及 Spring获取Bean的几种方式
- [转发]在 Java Web 项目中选择使用 Slf4J 通用日志框架
- spring aop的使用(注解方式以及基于xml配置方式)