iBATIS&Spring合奏(五)--整合lucene搜索表字段内容

之所以加上lucene来建立搜索引擎,是因为其轻便简单以及快速。Lucene作为开源社区很好的建立搜索引擎的框架也给我们(Java程序员)提供了很友好的方式。这次介绍的也是简单的整合,对于Lucene的一些复杂特性还请大家仔细阅读文档以及书籍或求助Google以及源码。
其实这篇博客早就要写了,只是最近遇到了非常纠结的事情,那就是要考一门我完全不会的课——计量经济学。都是模型,公式,觉得数学和经济学真的是一家人,而我和它们毫无缘分。今天公益劳动实验室的老师说搞技术的工作累,费心费脑,其实觉得也到差不多各行各业,都要承受压力,都要有自己的突破。。。God,跑题……
下面的示例是建立在前面几篇文章的基础上的。原来的项目代码可以在前几篇下载得到,就免去看那些罗嗦了。还是一步一步开始。
1)在数据库中建立内容字段。原来的User表中加入一个note字段就OK。因为我们要对这个字段的存储内容进行索引。有人说有数据库我可以直接搜索数据库啊,模糊查询是吧,效率不高吧,做一次IO和做一次数据库操作,还是模糊查询……当然先不考虑这么多,我们只是先借用DB的内容。到时候搜索的时候就完全操作文件了,数据库就不知道我们做了什么……
2)调整POJO,VO结构加入note属性。
3)建立索引。每次搜索不是全文搜索,我们要对需要被搜索的内容先建立索引,然后搜索会通过索引来查询内容。搜索的算法决定了效率。为了简便,就在JUNIT中试一下吧。不过首先我们要有个存储索引文件的路径以及一个分析器,分析器有很多种,这里选用按照词库检索的方式的分析器。当然搜索的时候也要用同样的分析器。这个后面说。准备代码如下:

@SuppressWarnings("unused")
private final String INDEXPATH = "g:\\userindex";
@SuppressWarnings("unused")
private Analyzer analyzer = new MMAnalyzer();
UserDao service;

上面的UserDao可以参看前几章,是需要通过Spring注入进来滴。然后看看我们的建立索引的代码:

@Test
public void createIndex() {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
service = (UserDao) ctx.getBean("userDao");
List<User> list = service.getAllUsers();
ctx.destroy();
try
{
Directory directory = FSDirectory.getDirectory(INDEXPATH);
IndexWriter indexWriter = new IndexWriter(directory, analyzer ,true, IndexWriter.MaxFieldLength.LIMITED);

long begin = new Date().getTime();
for(User u: list)
{
Document doc = new Document();
String username = u.getUsername() == null ? "" : u.getUsername().trim();
String note = u.getNote() == null ? "" : u.getNote();
String company = u.getCompany() == null ? "" : u.getCompany();
doc.add(new Field("username", username, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.YES));
doc.add(new Field("note", note, Field.Store.COMPRESS, Field.Index.ANALYZED, Field.TermVector.NO));
doc.add(new Field("company", company, Field.Store.YES,Field.Index.NOT_ANALYZED,Field.TermVector.YES));
indexWriter.addDocument(doc);
}
long end = new Date().getTime();
System.out.println(">>> 1.存入索引完毕.. 共花费:" + (end - begin) +"毫秒...");
//优化,合并
indexWriter.optimize();
indexWriter.close();

}catch(Exception e){
e.printStackTrace();
}

}


4)写搜索方法。搜索方法我们返回User对象,当然是内存中的对象,我们没有填充所有的字段,取回对象只是为了在前段方便获取(因为我们用的AMF协议是建立在HTTP基础上通过序列化与反序列化传输对象的方式来进行远程访问的)。为了不造成困扰在此说明。先看下代码,代码放在了UserService类中,在前段可以直接调用,当然也是拜托Spring帮助我们来管理切面逻辑的类。方法代码如下:

public List<User> getUsersByQuery(String query) {
try {
List<User> qlist = new ArrayList<User>();
String fieldName1 = "username";
String fieldName2 = "note";
IndexSearcher indexSearcher = new IndexSearcher(INDEXPATH);

// QueryParser parser = new QueryParser(fieldName, analyzer); //单字
// key 搜索
// Query queryOBJ = parser.parse(query);
// System.out.println(">>> 2.开始读取索引... ... 通过关键字:【 " + query + " 】");
// long begin = new Date().getTime();

// 下面的是进行username,note两个范围内进行收索.
BooleanClause.Occur[] clauses = { BooleanClause.Occur.SHOULD,
BooleanClause.Occur.SHOULD };
Query queryOBJ = MultiFieldQueryParser.parse(query, new String[] {
"username","note"}, clauses, new MMAnalyzer());// parser.parse(query);
Filter filter = null;

// ################# 搜索相似度最高的记录 ###################
TopDocs topDocs = indexSearcher.search(queryOBJ, filter, 1000);
// TopDocs topDocs = indexSearcher.search(queryOBJ , 10000);
System.out.println("*** 共匹配:" + topDocs.totalHits + "个 ***");

User user = null;

// 输出结果--按分数计算
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
Document targetDoc = indexSearcher.doc(scoreDoc.doc);
user = new User();

// 设置高亮显示格式
// SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter(
// "<font color='red'><strong>", "</strong></font>");
SimpleHTMLFormatter ff=new SimpleHTMLFormatter("<",">");
// /* 语法高亮显示设置 */
Highlighter highlighter = new Highlighter(ff,new QueryScorer(queryOBJ));
highlighter.setTextFragmenter(new SimpleFragmenter(100));

// 设置高亮 设置 username 字段
String username = targetDoc.get("username");
String note=targetDoc.get("note");
TokenStream titleTokenStream = analyzer.tokenStream(fieldName1,
new StringReader(username));
String highLightName = highlighter.getBestFragment(
titleTokenStream, username);
TokenStream noteTokenStream = analyzer.tokenStream(fieldName2,
new StringReader(note));
String highLightNote = highlighter.getBestFragment(
noteTokenStream, note);
//
if (highLightName == null)
highLightName = username;
if(highLightNote == null)
highLightNote = note;

user.setUsername(highLightName);
user.setNote(highLightNote);

qlist.add(user);
}

// long end = new Date().getTime();
// System.out.println(">>> 3.搜索完毕... ... 共花费:" + (end - begin)
// + "毫秒...");

indexSearcher.close();

return qlist;

} catch (Exception e) {
e.printStackTrace();
return null;
}

}

具体的lucene API就不多做解释了。因为我们用Flex做前端所以高亮器就没有设置。对于jsp还是可以应用的,所以代码注释掉了。我们可以看到MultiFieldQueryParser构造器中传入了我们建立索引时候的MMAnalyzer分析器,如前所说,他们是要一样的。就像序列化与反序列化,他们要遵循同样的规则一样。要不搜索的时候那抱歉了,我(Query)只认识MMAnalyzer分词后的索引啦。
5)前端调用。这个和前面没什么差别了。还是要RPC对象。spritis.mxml文件的RPC配置RemoteObject只要添加一个方法如下:

<mx:method name="getUsersByQuery" result="getUsersByQuery_resultHandler(event)">
<mx:arguments>
<arg1> {query.text}</arg1>
</mx:arguments>
</mx:method>

同样AS代码对于result事件的响应代码如下:

private function getUsersByQuery_resultHandler(event:ResultEvent):void
{
users = event.result as ArrayCollection;

}

接下来mxml语言发挥作用,直接调用UserService的搜索方法就好啦:

<mx:HBox fontSize="13">
<mx:Button label="Search Users:" click="user.getUsersByQuery(query.text)"/>
<mx:TextInput id="query" width="100"/>
</mx:HBox>

注意到了click事件响应的东西是神马了吗,就是我们的远程服务器端方法名,传入参数是我们用户在TextInput 控件中输入的东西。
6)运行,测试。下面的工作就是启动Tomcat,这只可爱的小猫咪,然后在搜索的输入框输入“麻花”二字,毫无悬念地,我们会在DataGrid控件中看到检索出的东西,只是作为内存中的User对象输出,而非数据库中的啦。简单看一下运行后的结果图吧,比较粗糙,情有审美眼光的童鞋谅解……我最怕写前端了。

[img]http://dl.iteye.com/upload/attachment/373744/d8b9e3ee-bd20-3de5-a02b-02f0de0cf2c2.jpg[/img]

可以看到关键字麻花被括起来了,那是高亮器,简易版本的。因为麻花是一个词典中的词,所以被搜索出来了,如果输入“麻”就没有结果了,因为我们采用的是MM词典分词。当然lucene有很多种分词器,也可以写自己的分析器,过滤器(像百度之类的),还可以写自己的结果排序规则(像百度广告在前面显示)。这里不详述了,只做简单介绍。
写到这里我还是很迷茫,因为明天要考计量经济学,我很佩服研究出那些模型的老外。看来还是兴趣为先驱,坚持是硬道理。现在听到了SHE的《爱就对了》,其实敲代码和恋爱都是一样的也许,都要承受一定的痛苦期,坚持,专心,过程中也会有磕磕绊绊,但是每次都克服过来,都会有很好的结果的。好吧,敲就对了……
最后,老规矩不变,给所有可爱的猿类们一个轻松的小笑话(这个笑话有点长请大家坚持,,,):

某日,老师在课堂上想考考学生们的智商,就问一个男孩: “树上有十只鸟,开枪打死一只,还剩几只?”
男孩反问:“是无声的枪,还是其他没有声音的枪么?”
“不是.”
“枪声有多大?”
“80~100分贝.”
“那就是说会震的耳朵疼?”
“是.”
“在这个城市里打鸟犯不犯法?”
‘不犯.”
“您确定那只鸟真的被打死啦?”
“确定.”老师已经不耐烦了,”拜托,你告诉我还剩几只就行了,OK?”
“OK.鸟里有没有聋子?”
“没有.”
“有没有鸟智力有问题,呆傻到听到枪响不知道飞的?”
“没有,智商都在200以上!”
“有没有关在笼子里的?”
“没有.”
“边上还有没有其他的树,树上还有没有其他鸟?”
“没有.” “方圆十里呢?” “就这么一棵树!”
“有没有残疾或饿的飞不动的鸟?”
“没有,都身体倍棒.”
“算不算怀孕肚子里的小鸟?”
“都是公的.”
“都不可能怀孕?”
“………,决不可能.”
“打鸟的人眼里有没有花?保证是十只?”
“没有花,就十只.” 老师脑门上的汗已经流下来了,
下课铃响起,但男孩仍继续问:“有没有傻的不怕死的?”
“都怕死.”
“有没有因为情侣被打中,自己留下来的?”
“笨蛋,之前不是说都是公的嘛!”
“同志可不可以啊!”
“………….,性取向都很正常!”
“会不会一枪打死两只?”
“不会.”
“一枪打死三只呢?”
“不会.”
“四只呢?”
“更不会!”
“五只呢?”
“绝对不会!!!”
“那六只总有可能吧?”
“除非你他妈的是猪生的才有可能!一枪只能打死一只!”
“…好吧,那么所有的鸟都可以自由活动么?”
“完全可以.”
“它们受到惊吓起飞时会不会惊慌失措而互相撞上?”
“不会,每只鸟都装有卫星导航系统,而且可以自动飞行.”
“恩,如果您的回答没有骗人,”学生满怀信心的回答,“打死的鸟要是挂在树上没掉下来,那么就剩一只,如果掉下来,就一只不剩.”
老师推推眼镜,强忍着要昏倒的感觉,颤抖地说道:“你可以去当程序员了……”
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值