`
ahuaxuan
  • 浏览: 633666 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

深入浅出 jackrabbit 十 查询概览

阅读更多
/**
*author: ahuaxuan
*date: 2009-06-20
*/
查询进阶
在第一篇文章的例子中,我们看到jackrabbit中存放数据的流程还是比较清晰的,而且我们也基本确定了其中数据的存储方式,一颗m叉树。

正如db一样,insert都是看上去相对简单的,select相对总是没有这么简单的,如同关系数据库一样,jackrabbit中查询也有类似的流程,一般来说分成三个部分:1解析查询语句,2查询,3返回结果。这个应该是一个比较通用的模型。Jackrabbit的search部分亦不过如此。下面我们来看一个search的小例子。
public static void search(String co) throws Exception {
		Repository repository = new TransientRepository();

		Session session = repository.login(new SimpleCredentials("username",
				"password".toCharArray()));
		try {

			// path query
			Workspace workSpace = session.getWorkspace();
			QueryManager queryMgr = workSpace.getQueryManager();
			//查询语句
			String queryPath2 = "//world [jcr:contains(., 'Maven')] order by jcr:score() descending, @name ascending";
			
			//编译查询语句
			Query query = queryMgr.createQuery(queryPath2,
					Query.XPATH);
			
			//执行查询任务
			QueryResult queryResult = query.execute();
			
			//得到结果
			RowIterator rows = queryResult.getRows();
			String result = "";
			while (rows.hasNext()) {
				Row resultRow = rows.nextRow();
				
/*				Value[] values = resultRow.getValues();
				for (Value value : values) {
					System.out.println(" Value ------ " + value.getString());
				}
*/				//
				// --------------------------------------------------------------------------
				TimerMasker tm = new TimerMasker();
				result = resultRow.getValue("rep:excerpt(jcr:data)").getString();
				tm.print("----------------------------------"); //
			}
		} finally {
			session.logout();
		}
	}

这个例子很简单,使用xpath语法来查询world节点中包含指定字符串的同层节点。然后按照指定的属性排序并返回。从上面的例子中我们大概就只能得到这么多信息了,但是我想爱好技术的你一定不会满足,也一定不会止步于这个小小的例子。如果同第一篇文章所讲,我们不能只看到事物的表面,而是要通过这个表面来挖掘其背后的本质。

所以接下来让我们来看看事物背后的本质。众所周知,只要是数据存储方案,那么必定离不开一个东西,那就是索引,索引可以帮助我们快速找到我们想要的数据。那么jackrabbit是怎么根据xpath语法就找到对应的数据的呢,无疑,它也是在运用搜索技术,而且通过它的package列表我们可以看到,它其实就是利用lucene来完成了它的查询模块。也就是说在jackrabbit中,利用了xpath和lucene实现了它的查询模块。

搞清楚了这两点,我们就可以继续往下深入了。导入源代码,让我们从createQuery方法开始,这个方法很重要,可以说是非常重要,而且这个方法中也包含了一门基础学科-编译原理(jackrabbit中是使用javacc来生成xpath的词法语法分析类的org.apache.jackrabbit.core.query.xpath.Xpath,并且它的语法文件xpath.jj长达9000行,ahuaxuan暂时还没有这个闲心看下去)。createQuery方法的两个参数,一个是查询语句,还有一个是语句的语法,目前jackrabbit支持xpath语法和sql语法,他们在整个流程上属于一个可以相互替换的节点上,就是生成AST。再生产语法树之后,会生成查询节点树,这个颗树中包含了lucene需要的很多信息,比如需要搜索的key word,搜索时的排序字段等等。所以在第一大步骤createquery中我们可以细分为两个小的步骤:
1 编译query statement,生成AST
2 通过AST生成querynode

通过上面的两个主要步骤,我们顺利的得到了query对象,query对象就包含的查询所需要的基本条件了,很显然它和hibernate的query对象有异曲同工之妙。都是通过一个查询语句来生产query对象,然后query对象中包含了查询的所有信息,一旦执行execute方法,就真正的触发了的查询的流程。

说到这里就不得不看一下一个关键的方法(org.apache.jackrabbit.core.query.lucene.QueryImpl):
public QueryResult execute(long offset, long limit) throws RepositoryException {
        if (log.isDebugEnabled()) {
            log.debug("Executing query: \n" + root.dump());
        }
        TimerMasker tk = new TimerMasker();
        // 这里创建lucene的query对象,同时,这是一个BooleanQuery
        Query query = LuceneQueryBuilder.createQuery(root, session,
                index.getContext().getItemStateManager(),
                index.getNamespaceMappings(), index.getTextAnalyzer(),
                propReg, index.getSynonymProvider(),
                index.getIndexFormatVersion());
		 //得到排序信息
        OrderQueryNode orderNode = root.getOrderNode();

        OrderQueryNode.OrderSpec[] orderSpecs;
        if (orderNode != null) {
            orderSpecs = orderNode.getOrderSpecs();
        } else {
            orderSpecs = new OrderQueryNode.OrderSpec[0];
        }
        Name[] orderProperties = new Name[orderSpecs.length];
        boolean[] ascSpecs = new boolean[orderSpecs.length];
        for (int i = 0; i < orderSpecs.length; i++) {
            orderProperties[i] = orderSpecs[i].getProperty();
            ascSpecs[i] = orderSpecs[i].isAscending();
        }

        //创建查询结果,事实上,这个一部才是真正执行lucene query的//地方。这一点可以从QueryResultImpl的构造方法中看出来,构造方法中//的getresult其实就是真正执行lucene search的地方,比较顺理成章
        QueryResultImpl qri = new QueryResultImpl(index, itemMgr,
                session, session.getAccessManager(),
                this, query, new SpellSuggestion(index.getSpellChecker(), root),
                getSelectProperties(), orderProperties, ascSpecs,
                getRespectDocumentOrder(), offset, limit);
        tk.print("execute");
        return qri;
}


通过上面的代码注释,那么我们可以得到一个有效的信息,那就是在ahuaxuan提供的示例代码中执行query.execute()的时候,这个时候,我们需要的结果其实就已经加载到内存中了。有的人会感到疑惑,为什么上面的QueryResultImpl中带有这么多构造参数,其实那些对象是用来组装lucene出来的结果的。因为放到索引中的数据并不是node的所有信息,比如一个node,因为放到索引中只是nodeid,其对应的node对象还是需要通过itemMgr这样的对象来得到。

这样我们又确定了第二大步骤包含了二个步骤:
1 根据querynode生成lucene的query对象
2 执行lucene的query对象并得到结果集


现在数据,也有了,那么接下来做什么呢?当然是遍历数据,拿出结果来,显然这个和jdbc的工作流程有得一拼啊(看来这里的时候,大家对查询的使用是不是比较清楚了,它和很多持久化框架在流程上是相当一致的,熟悉了一个之后,再来看其他的,学习的速度明显得到加强,当前前提是你已经充分理解了某个框架的内部原理)。那么接下来的事情只有这些:
//得到结果
			RowIterator rows = queryResult.getRows();
			while (rows.hasNext()) {
				Row resultRow = rows.nextRow();
				resultRow.getValue("rep:excerpt(jcr:data)").getString();
			}


看上去很简单。实际上也比较简单,但是其中确有一个ahuaxuan认为比较愚蠢的设计。拿这个例子来说,接下来的流程就是得到得到数据,并作一些客户端需要的操作,比如高亮。
通过代码的阅读:在这个大步骤中,又细分为以下步骤:
1 根据getvalue中参数判断查询的内容,比如jcr:score,jcr:path等,这些属性很容易就能得到,可以说是现成的,稍微看一下代码就知道他们是怎么获得的,而在上面的例子中,我们是要找jcr:data这个属性,并高亮之。而且这个属性也是最复杂的,其他属性比较简单,下面我们把注意力集中到这个属性。Jcr:data是jackrabbit的内在属性,目前一个node只能有一个这样的属性,这个属性中存放着二进制文档提取之后的fulltext的信息(当然你可以手动指定这个属性存放什么text)。现在我们就是要在结果集中高亮这个我们的keyword->’Maven’,之前ahuaxuan将maven的一篇文档放到jackrabbit中了,所以现在要高亮这个结果集。

要高亮之,必须得到Maven在这个text中的offset,所以就出现了下面这段代码:
Reader r = new StringReader(text);
        TokenStream ts = index.getTextAnalyzer().tokenStream("", r);
        Token t;
        try {
            while ((t = ts.next()) != null) {
                TermVectorOffsetInfo[] info =
                        (TermVectorOffsetInfo[]) termMap.get(t.termText());
                if (info == null) {
                    info = new TermVectorOffsetInfo[1];
                } else {
                    TermVectorOffsetInfo[] tmp = info;
                    info = new TermVectorOffsetInfo[tmp.length + 1];
                    System.arraycopy(tmp, 0, info, 0, tmp.length);
                }
                info[info.length - 1] = new TermVectorOffsetInfo(
                        t.startOffset(), t.endOffset());
                termMap.put(t.termText(), info);
            }
        } catch (IOException e) {
            // should never happen, we are reading from a string
        }


就是这段代码,可能会给我们带来一些些小麻烦。得到offset之后,那么下一步肯定就是高亮了,高亮的逻辑简要说来就是遍历这个offset,并根据每个offset指定的start和end来插入高亮的标识。

看上去,一切顺理成章,我们的例子是使用1m的maven文档,测试的结果是:
createTermPositionVector >>>-------- total nano seconds : 373531154

highlight:2761 >>>-------- total nano seconds : 146860414


也就是说计算每个term和对应的offset集合用了370ms,是整个搜索过程耗时的75%以上,如果把这一步省掉,可以节约75%的时间,那么如何省掉呢,两个方案,一,修改analyser,只找keyword和对应的offset,二,搜索的时候将term和对应offset保存起来。这样此处的370ms应该可以大大变小。经过ahuaxuan的修改之后,将offset保存在index之中,然后高亮的时候直接拿出来,避免了实时分词。测试的结果如下
new createTermPositionVector  >>>-------- total nano seconds : 4003012

也就是说这个方法从370ms变成了4ms,速度大大加快(虽然索引文件会变大一些)。

除此之外,在高亮的时候我们可以不用高亮全文,也就是取前10个offset,然后高亮之,再次运行相同的测试:
highlight:10 >>>-------- total nano seconds : 1264721

高亮从146ms变成了1ms


这样虽然只会高亮前10个关键字,但是速度从146ms变成了1ms.对于用户来说,高亮一个摘要并非不能接受。带来的好处是:
1 用户可以更加快速的得到结果集
2 大家知道,文字处理是非常消耗cpu的,通过这种方式也一定程度上节约了服务器上的cpu
对服务器,对客户都好,何乐而不为

通过以上两处的修改,原来高亮1m的文本需要530ms,而现在只需要5ms,提高了100倍。ahuaxuan以为,为了速度,这样的修改是值得的。

说到这里,查询这块可以告一段落了:
下面总结一下查询过程中的主要步骤:
1 编译query statement,生成AST
2 通过AST生成querynode
3 根据querynode生成lucene的query对象
4 执行lucene的query对象并得到结果集
5 遍历中:通过itemmanager和查询结果中的nodeid得到需要的node
6 生成offset
7 高亮之

由于ahuaxuan水平有限,见解未必全面,如有错误,请大家拍砖。
分享到:
评论
5 楼 lnjzliulei 2013-06-03  
兄弟,有源代码吗?48703577@qq.com请给发一份,谢谢!
4 楼 ahuaxuan 2009-12-14  
无量胖佛 写道
有你写的源码可以给发一个吗?
wddsmzx@gmail.com
谢谢了 老哥

这段代码我也没有了,因为我发现这样做还是有问题,这样做会使lucene的index文件增加3倍,其中offset和解析出来的文本是不需要放进去的,索引文件越大,那么搜索或者以后改造成分布式的困难就越高。我比较推荐的做法是高亮还是放在客户端做吧
3 楼 无量胖佛 2009-12-10  
有你写的源码可以给发一个吗?
wddsmzx@gmail.com
谢谢了 老哥
2 楼 ahuaxuan 2009-06-20  
其实我是先看index的,index早于search,index的流程过两天写出来,我把search这部分改了一下,把创建offset放到index的流程中了,highlighter也变成只取10个offset来作,在大文本的情况下,查询速度从原来的500ms缩小为5ms
1 楼 lxiaodao 2009-06-20  
不错文章,对于query有了一定得了解,如果能有分析一下index的原理的文章就更好了。

相关推荐

Global site tag (gtag.js) - Google Analytics