让hibernate支持 sql_calc_found_rows 一次完成分页与计数

本文介绍了一种使用Hibernate进行高效分页的方法,通过自定义修改使Hibernate支持MySQL的SQL_CALC_FOUND_ROWS关键字,从而优化分页查询的性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

用hibernate分页的时候遇到了个问题,就是获取总页数。

现在流行的办法是用Criteria,在排序前先分组计数并返回值,然后在添加排序、分页。举例来说:

	 Session session = sessionFactory.getCurrentSession();
	 Criteria c = session.createCriteria(UserModel.class);
	 c.add(Restrictions.ge("id", 10));
	 c.add(Restrictions.le("id",20));
	 int t = ((Long) c.setProjection(Projections.rowCount())
		      .uniqueResult()).intValue();
	 c.setProjection(null);
	 c.setMaxResults(4);
	 c.addOrder(Order.desc("id"));

注意中间有一个个setProjection(null),用来把之前的分组取消。这算是一个hack吧,虽然在排序前进行总数计数能节约一部分性能,但依然是两次查询。例子中的选择条件是相当简单的,如果复杂一些呢,再来几个many-to-one或者one-to-many子查询呢?

针对我用的是mysql,百度到一种解决方案[1]:用select found_rows(),可以得到上一次查询的结果数。如果在查询中加上SQL_CALC_FOUND_ROWS,就可以忽略limit,也就是说得到我们要的总条目数,也就能得到总页数!

但奈何SQL_CALC_FOUND_ROWS并不是hql关键字,语法解析的时候就被pass了。于是乎花了一天的时间追踪hibernate源码(大部分时间都浪费在官网下的源码,各种莫名其妙的错误,而且还少类!后来用了在grepcode.com下的hibernate源码),又简单学习了hibernate所用语言识别工具antlr,终于将SQL_CALC_FOUND_ROWS成功加入hibernate的抽象语法树AST。

所用hibernate版本号:4.1.6 final

所用依赖包见:http://grepcode.com/snapshot/repo1.maven.org/maven2/org.hibernate/hibernate-core/4.1.6.Final/

流程:

(1)配置antlr的环境变量,将antlr的jar包添加至环境变量的classpath中[2]

(2)修改hql.g(如果在grepcode上下载的源码,这些中间文件在jar包的一级目录里,如果是Hibernate官网上下的,在/project/hibernate-core/src/main/antlr中[3]):

在tokens代码块中添加:

SELECTCOUNT="sql_calc_found_rows";


修改selectClause的规则定义:

selectClause
	: SELECT^	// NOTE: The '^' after a token causes the corresponding AST node to be the root of the sub-tree.
		{ weakKeywords(); }	// Weak keywords can appear immediately after a SELECT token.
		(SELECTCOUNT)? (DISTINCT)? ( selectedPropertiesList | newExpression | selectObject )
	;

(3)修改hql-sql.g

修改selectClause的规则定义:

selectClause!
	: #(SELECT { handleClauseStart( SELECT ); beforeSelectClause(); } (s:SELECTCOUNT)? (d:DISTINCT)? x:selectExprList ) {
		#selectClause = #([SELECT_CLAUSE,"{select clause}"], #s, #d, #x);
	}
	;
(4)修改sql-gen.g

修改selectClause的规则定义:

selectClause
	: #(SELECT_CLAUSE (countRows)? (distinctA)? ( selectColumn )+ )
	;

在其后添加一条新的规则:

countRows
	: SELECTCOUNT { out("sql_calc_found_rows "); }
	;	

(5)按序用antlr运行上述3个词法文件

(6)将生成的*.java(一共7个)放至org.hibernate.hql.internal.antlr中,覆盖原文件。

(7)修改org.hibernate.hql.internal.ast.tree.SelectClause的getFirstSelectExpression方法:

protected AST getFirstSelectExpression() {
		AST n = getFirstChild();
		// Skip 'DISTINCT' and 'ALL', so we return the first expression node.
		while ( n != null && ( n.getType() == SqlTokenTypes.DISTINCT || n.getType() == SqlTokenTypes.ALL || n.getType() == SqlTokenTypes.SELECTCOUNT ) ) {
			n = n.getNextSibling();
		}
		return n;
	}

(8)大功告成,在hql中就可以使用 sql_calc_found_rows 了!~


关于found_rows()的并行支持,考虑到hibernate中用了事务,加上mysql推出此函数的目的就是用于获取总页数的场景,心里稍宽慰。

有文章说SQL_CALC_FOUND_ROWS并不一定提升查询性能[5],也有人做了类似实验并给出解释[6]。我懒,没自己做实验证实,凭感觉肯定用这种官方提供的专属函数要比两次查询快。跟着感觉走~


参考:

[1]:http://www.ooso.net/archives/342

[2]:http://www.ibm.com/developerworks/cn/java/j-lo-antlr/

[3]:http://calvin.iteye.com/blog/92007

[4]:antlr2.7.5中文文档:http://ishare.iask.sina.com.cn/download/explain.php?fileid=15440125

[5]:http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

[6]:http://hi.baidu.com/thinkinginlamp/item/b122fdaea5ba23f614329b14

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值