解决hibernate对Sql Server分页慢的问题
一、hibernate分页 hibernate对MsSql的伪分页
分页是web项目中比不可少的一个功能,数据量大的时候不能全部展示必然要用到分页技术。相信大家对hibernate中的分页都不陌生:
- public Query setMaxResults(int maxResults);
- public Query setFirstResult(int firstResult);
只要调用了这两个方法并设置好参数,hibernate自动分页完全屏蔽了底层数据库分页技术,这也是众多开发者喜欢hibernate的原因之一。
项目开发中遇到一个奇怪的问题。数据库采用的是Sql Server 2005,也设置了上面两个参数,可是每次发送到数据库端的SQL语句都是select top ....语句。即便是查询第10w条,也只有一个select top 语句,不免引起对hibernate实现sql server分页的怀疑。hibernate针对不同数据库实现的分页方法封装在对应数据库的方言里,通过getLimitString方法转化成对应数据库的分页算法。
以常见的Mysql数据库的方言MySQLDialect为例:
- public String getLimitString(String sql, boolean hasOffset) {
- return new StringBuffer( sql.length() + 20 )
- .append( sql )
- .append( hasOffset ? " limit ?, ?" : " limit ?" )
- .toString();
- }
采用了大家熟悉的mysql的limit进行分页。
Oracle数据库的方言Oracle9iDialect:
- StringBuffer pagingSelect = new StringBuffer( sql.length()+100 );
- if (hasOffset) {
- pagingSelect.append("select * from ( select row_.*, rownum rownum_ from ( ");
- }
- else {
- pagingSelect.append("select * from ( ");
- }
- pagingSelect.append(sql);
- if (hasOffset) {
- pagingSelect.append(" ) row_ where rownum <= ?) where rownum_ > ?");
- }
- else {
- pagingSelect.append(" ) where rownum <= ?");
- }
利用Oracle的rownum 结合三层嵌套查询完成分页。这个三层是Oracle最经典高效的分页算法。
可是针对Sql Server的方言SQLServerDialect:
- public String getLimitString(String querySelect, int offset, int limit) {
- if ( offset > 0 ) {
- throw new UnsupportedOperationException( "query result offset is not supported" );
- }
- return new StringBuffer( querySelect.length() + 8 )
- .append( querySelect )
- .insert( getAfterSelectInsertPoint( querySelect ), " top " + limit )
- .toString();
- }
揉揉眼睛、再揉揉,没错,只出现了一个top语句。这就意味着如果查询第10w页的数据,需要把前10w页数据全部提取出来。hibernate针对sql server的分页是伪分页,所以随着数据量日益增加用户抱怨系统速度慢,程序员抱怨hibernate性能低,dba抱怨开发人员sql功底太浅。
不知道hibernate开发组,出于什么目前或情况没有真正提供sql server的分页技术,那我们自己来实现。
方言类:
- public class SQLServer2005Dialect extends SQLServerDialect {
- /**
- *
- * 是否需要绑定limit参数?
- *
- * 在SQL Server中使用top时不能使用参数表示top条数,而使用ROW_NUMBER()则需要提供limit参数
- */
- private ThreadLocal<Boolean> supportsVariableLimit = new ThreadLocal<Boolean>();
- public SQLServer2005Dialect() {
- registerFunction("bitand", new BitAndFunction());
- registerFunction("bitxor", new BitXorFunction());
- registerFunction("bitor", new BitOrFunction());
- setSupportsVariableLimit(false);
- }
- /**
- *
- * <p>
- * 设置是否先绑定limit参数。
- * </p>
- *
- * @param first
- */
- private void setSupportsVariableLimit(boolean first) {
- this.supportsVariableLimit.set(Boolean.valueOf(first));
- }
- /**
- *
- * <p>
- * 获取sql中select子句位置。
- * </p>
- *
- * @param sql
- *
- * @return int
- */
- protected static int getSqlAfterSelectInsertPoint(String sql) {
- int selectIndex = sql.toLowerCase().indexOf("select");
- int selectDistinctIndex = sql.toLowerCase().indexOf("select distinct");
- return selectIndex + ((selectDistinctIndex == selectIndex) ? 15 : 6);
- }
- public boolean supportsLimitOffset() {
- return true;
- }
- /*
- * Hibernate在获得Limit String(已添加了limit子句)后,如果此方法返回true,
- *
- * 则会添加额外的参数值(ROW_NUMBER()范围)(策略可能是这样:有offset设置两个参数值,没有设置一个参数值)
- */
- public boolean supportsVariableLimit() {
- return ((Boolean) this.supportsVariableLimit.get()).booleanValue();
- }
- public boolean useMaxForLimit() {
- return true;
- }
- /**
- * 首页top,以后用ROW_NUMBER
- */
- public String getLimitString(String query, int offset, int limit) {
- setSupportsVariableLimit(offset > 0);
- if (offset == 0) {
- return new StringBuffer(query.length() + 8).append(query).insert(
- getSqlAfterSelectInsertPoint(query), " top " + limit)
- .toString();
- }
- return getLimitString(query, offset > 0);
- }
- public String getLimitString(String sql, boolean hasOffset) {
- int orderByIndex = sql.toLowerCase().lastIndexOf("order by");
- if (orderByIndex <= 0) {
- throw new UnsupportedOperationException(
- "must specify 'order by' statement to support limit operation with offset in sql server 2005");
- }
- String sqlOrderBy = sql.substring(orderByIndex + 8);
- String sqlRemoveOrderBy = sql.substring(0, orderByIndex);
- int insertPoint = getSqlAfterSelectInsertPoint(sql);
- return new StringBuffer(sql.length() + 100)
- .append("with tempPagination as(")
- .append(sqlRemoveOrderBy)
- .insert(
- insertPoint + 23,
- " ROW_NUMBER() OVER(ORDER BY " + sqlOrderBy
- + ") as RowNumber,")
- .append(
- ") select * from tempPagination where RowNumber>? and RowNumber<=?")
- .toString();
- }
- }
最后在配置Hibernate的时候在 hibernate.cfg.xml 配置 dialect 使用自己的SQLServer2005Dialect类
或在与spring的集成配置文件中
<prop key="hibernate.dialect">com.common.SQLServer2005Dialect</prop>
在分页时,就可以达到很好的效率了。
第一页采用top分页,以后采用ROW_NUMBER分页第一页以上查询要求sql中必须含有排序子句。
这只是在项目过程中采用的sql server遇到的该问题,如果使用mysql,oracle则不会遇到这个问题。
解决hibernate对Sql Server分页慢的问题的更多相关文章
- hibernate 对 sql server 2005 分页改进
Hibernate 可以实现分页查询 如下 Query q = session.createQuery("from Cat as c"); q.setFirstResult(100 ...
- SQL Server游标 C# DataTable.Select() 筛选数据 什么是SQL游标? SQL Server数据类型转换方法 LinQ是什么? SQL Server 分页方法汇总
SQL Server游标 转载自:http://www.cnblogs.com/knowledgesea/p/3699851.html. 什么是游标 结果集,结果集就是select查询之后返回的所 ...
- SQL SERVER 分页方法
最近项目中需要在SQL SERVER中进行分页,需要编写分页查询语句.之前也写过一些关于分页查询的语句,但是性能不敢恭维.于是在业务时间,在微软社区Bing了一篇老外写的关于SQL SERVER分页的 ...
- 解决:安装SQL Server 2008 Native Client遇到错误(在Navicat premium新建sqlserver连接时 需要):An error occurred during ...HRESULT: 0x80070422(注意尾部的错误号)
解决:安装SQL Server 2008 Native Client遇到错误(在Navicat premium新建sqlserver连接时 需要):An error occurred during . ...
- SQL Server分页模板
SQL Server分页模板 WITH T AS ( SELECT ROW_NUMBER() OVER(ORDER BY AlbumId ) AS row_number, * FROM (SELECT ...
- SQL Server分页语句ROW_NUMBER,读取第4页数据,每页10条
SQL Server分页语句ROW_NUMBER,读取第4页数据,每页10条 SELECT Id,[Title],[Content],[Image] FROM ( SELECT ROW_NUMBER( ...
- SQL server分页的四种方法
SQL server分页的四种方法 1.三重循环: 2.利用max(主键); 3.利用row_number关键字: 4.offset/fetch next关键字 方法一:三重循环思路 先取前20页, ...
- 二、SQL Server 分页
一.SQL Server 分页 --top not in方式 select top 条数 * from tablename where Id not in (select top 条数*页数 Id f ...
- SQL server分页的四种方法(算很全面了)
这篇博客讲的是SQL server的分页方法,用的SQL server 2012版本.下面都用pageIndex表示页数,pageSize表示一页包含的记录.并且下面涉及到具体例子的,设定查询第2 ...
随机推荐
- [办公应用]如何在WORD中让英文网址可以在字符中间换行
有时候我们写文章,存在中英文混合录入的情况.一般情况下,office 2003的word软件中,会自作聪明的避免单词断行显示,也就是说它会默认尽量把一个单词显示在某一行内,从而避免单词被分开.但有时候 ...
- nhibernate的关系
用nhibernate,觉得比较难把握的是其中表间的关系. 我用的是Fluently Nhibernate,直接用代码,而不是XML来书写代码与数据表的映射.其中表间关系有3种: 1.Referenc ...
- UVA 437 The Tower of Babylon巴比伦塔
题意:有n(n≤30)种立方体,每种有无穷多个.要求选一些立方体摞成一根尽量高的柱子(可以自行选择哪一条边作为高),使得每个立方体的底面长宽分别严格小于它下方立方体的底面长宽. 评测地址:http:/ ...
- ABAP ALV F4帮助
ALV F4帮助, 选值保存到ALV. TYPE-POOLS:slis. CLASS lcl_event_receiver DEFINITION DEFERRED. DATA: gt_fcat TYP ...
- iOS设备,fixed布局出问题
window.deviceId = '{{$deviceId}}'; window.iOS = navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? t ...
- caioj1230: [图论补充]哈密顿路径
保存模版 #include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> ...
- POJ3616 Milking Time —— DP
题目链接:http://poj.org/problem?id=3616 Milking Time Time Limit: 1000MS Memory Limit: 65536K Total Sub ...
- UVA315 Network —— 割点
题目链接:https://vjudge.net/problem/UVA-315 A Telephone Line Company (TLC) is establishing a new telepho ...
- I.MX6 su.c 测试
/************************************************************************* * I.MX6 su.c 测试 * 说明: * 今 ...
- bzoj 3991 寻宝游戏
题目大意: 一颗树 有一个点的集合 对于每个集合的答案为 从集合内一个点遍历集合内所有点再返回的距离最小值 每次可以选择一个点 若在集合外便加入集合 若在集合内就删除 求每次操作后这个集合的答案 思路 ...