`
my_corner
  • 浏览: 84472 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Hibernate的优化:分页

阅读更多
有同学强调我对sql server的分页不了解,在mssql 2000之前只能通过top进行分页。我没有说top分页不对,我是说在hibernate的sql server方言里面,针对sql server 的top分页是不对的。至少在我使用的版本里是不对的。而且有很多同学也遇到和我类似的问题。请大家打开hibernate的showsql,看看生成的sql 语句再来判断正确还是错误,谢谢
提到Hibernate,好多人都用“垃圾”,“效率太低”这样的字眼评价。其实任何一个框架的产生都是有原因的,这些为企业级开发产生的框架最初只是为了简化开发,或许会有一些影响性能的地方。但是通过配置和巧妙的办法,完全可以规避这些问题,发挥最大的效果。至少目前我的项目,日均pv在500w左右目前还没发现什么问题。
一、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();
	}
}

函数:
public class BitAndFunction implements SQLFunction {
	public Type getReturnType(Type type, Mapping mapping) {
		return Hibernate.INTEGER;
	}

	public boolean hasArguments() {
		return true;
	}

	public boolean hasParenthesesIfNoArguments() {
		return true;
	}

	public String render(List args, SessionFactoryImplementor factory)
			throws QueryException {
		if (args.size() != 2) {
			throw new IllegalArgumentException(
					"BitAndFunction requires 2 arguments!");
		}
		return args.get(0).toString() + " & " + args.get(1).toString();
	}
}

public class BitOrFunction implements SQLFunction {
	public Type getReturnType(Type type, Mapping mapping) {
		return Hibernate.INTEGER;
	}
	public boolean hasArguments() {
		return true;
	}
	public boolean hasParenthesesIfNoArguments() {
		return true;
	}
	public String render(List args, SessionFactoryImplementor factory)
			throws QueryException {
		if (args.size() != 2) {
			throw new IllegalArgumentException(
					"BitOrFunction requires 2 arguments!");
		}
		return args.get(0).toString() + " | " + args.get(1).toString();
	}
}

public Type getReturnType(Type type, Mapping mapping) {
		return Hibernate.INTEGER;
	}
	public boolean hasArguments() {
		return true;
	}
	public boolean hasParenthesesIfNoArguments() {
		return true;
	}
	public String render(List args, SessionFactoryImplementor factory)
			throws QueryException {
		if (args.size() != 2) {
			throw new IllegalArgumentException(
					"BitXorFunction requires 2 arguments!");
		}
		return args.get(0).toString() + " ^ " + args.get(1).toString();
	}


打包测试,一切正常。第一页采用top分页,以后采用ROW_NUMBER分页第一页以上查询要求sql中必须含有排序子句。
这只是在项目过程中采用的sql server遇到的该问题,如果使用mysql,oracle则不会遇到这个问题。
至此,项目中第一个问题解决!
分享到:
评论
28 楼 分离的北极熊 2010-07-29  
你别管HIBERNATE 是如何实现的,如果自己写SQL的话,把分页语句实现……

除了LS有说到的 row_number() over外,不用top,LZ可以尝试写一个分页来体验一下
27 楼 分离的北极熊 2010-07-29  
LZ好像对SQL SERVER 的分页语句不是很熟悉啊

mysql 是 limit
Oracle 是你文中所描述的 3层嵌套查询
而sql server 从久远的历史来看,就是用top来分页的, 你所看到的东西没什么不对的地方

26 楼 zeroxy 2010-07-28  
我记得Hibernate对Mssql的分页是这样处理的。比如说每页10条,取第10页。

select top 10
from (select Top 100 from Table_A order by id) a
order by a.id desc

确实是只用了top,但也不是全取出来。
25 楼 my_corner 2010-07-28  
ming123 写道
風一樣的男子 写道
my_corner 写道

return getLimitString(query, offset > 0);



这个直接
return getLimitString(query, true);

甚至
return getLimitString(query);

后面那个参数没看到有用啊

貌似是这样的,是不是lz的代码没粘贴全啊

呵呵,粘全了
24 楼 my_corner 2010-07-28  
風一樣的男子 写道
my_corner 写道

return getLimitString(query, offset > 0);



这个直接
return getLimitString(query, true);

甚至
return getLimitString(query);

后面那个参数没看到有用啊

这是重写的hibernate重写的Dialect这个抽象类中的方法。源代码中的两个方法如下:
/**
	 * Add a <tt>LIMIT</tt> clause to the given SQL <tt>SELECT</tt>
	 *
	 * @return the modified SQL
	 */
	public String getLimitString(String querySelect, boolean hasOffset) {
		throw new UnsupportedOperationException( "paged queries not supported" );
	}

	public String getLimitString(String querySelect, int offset, int limit) {
		return getLimitString( querySelect, offset>0 );
	}
23 楼 ming123 2010-07-28  
風一樣的男子 写道
my_corner 写道

return getLimitString(query, offset > 0);



这个直接
return getLimitString(query, true);

甚至
return getLimitString(query);

后面那个参数没看到有用啊

貌似是这样的,是不是lz的代码没粘贴全啊
22 楼 風一樣的男子 2010-07-27  
my_corner 写道

return getLimitString(query, offset > 0);



这个直接
return getLimitString(query, true);

甚至
return getLimitString(query);

后面那个参数没看到有用啊
21 楼 zzy9zzy 2010-07-27  
Sybase 12.5不支持物理分页,只能写存储过程处理,反映到Hibernate上就是整个查询代码都走样了,难写难看。
20 楼 笑我痴狂 2010-07-27  
学习了

期待继续更新

19 楼 ming123 2010-07-27  
java一般都选择mysql或者oracle吧
18 楼 my_corner 2010-07-27  
猪圈羊圈 写道
呵呵,楼主也发现这个问题了啊!
前面我们做项目也遇到这个问题。只能说开源的hibernate对微软的产品支持做的很水吧。

也许是认为sqlserver 不应和java配对。
17 楼 猪圈羊圈 2010-07-27  
呵呵,楼主也发现这个问题了啊!
前面我们做项目也遇到这个问题。只能说开源的hibernate对微软的产品支持做的很水吧。
16 楼 my_corner 2010-07-27  
sohuexe 写道
row_number() over 很容易处理分页的,不知道为啥那么多视而不见。

不是视而不见,2000的时候没有这个玩意,2005出来之后好多人不会用因此还沿用以前的写法。
15 楼 mzba520 2010-07-27  
嗯,学习了,关注一下
14 楼 sohuexe 2010-07-27  
row_number() over 很容易处理分页的,不知道为啥那么多视而不见。
13 楼 icefishc 2010-07-27  
liujun999999 写道
感觉sybase现在做得越来越不行了,我公司项目用ibatis,分页必须写一个存储过程来搞,sybase15.0里还有很多sql上的bug,一个标准的sql在oracle和mssql里都正常,但是跑到sybase里往往死活不行

标准sql不能跑只能说对标准支持不好。 说它是bug就有些冤枉它了。 Oracle和mssql server也不是所有的标准sql都支持
12 楼 liujun999999 2010-07-27  
感觉sybase现在做得越来越不行了,我公司项目用ibatis,分页必须写一个存储过程来搞,sybase15.0里还有很多sql上的bug,一个标准的sql在oracle和mssql里都正常,但是跑到sybase里往往死活不行
11 楼 魔力猫咪 2010-07-27  
icefishc 写道
my_corner 写道
魔力猫咪 写道
出现这种情况是因为Hibernate还一直沿用SQLServer2000时代的Dialect,没有为2005、2008编写新的Dialect。
可能因为Java阵营天生和微软犯冲吧。对对方的支持都相对差一些。

即便是沿用了SQLServer2000时代的Dialect也说不过去。不去看源代码,只看生成的sql,至少不能查询第几条就select top XXX 吧?只是一个top,在数据库层面来看,无论如何都是做不了分页的。

怀疑sqlserver 在2000时 还不支持在sub query中用top. 那时候又没有分析函数。 也没有什么太好的办法,除非用临时表。而临时表又有很多限制,不能在事物中使用。

2000时代是不支持rownumber的。SQLServer2000的JDBC驱动也是问题多多。直到2005驱动才得到改善,不过很多人还是更喜欢JTDS驱动。
10 楼 icefishc 2010-07-27  
引用

sybase和MsSql有些渊源,貌似当初微软和sybase合作,后来出来了MsSql。jtds驱动支持这个两个数据库,sybase的hibernate方言里有这么一句注释:
/**
* An SQL dialect compatible with Sybase and MS SQL Server.
* @author Gavin King
*/

嗯 在2000的时候他们还很像。 但后来区别就越来越大。到了05以后就可以把他们当作完全不同的数据库来学了。
9 楼 my_corner 2010-07-27  
icefishc 写道
my_corner 写道
liujun999999 写道
对啊,sybase方言应该也存在这个问题,学习了

sybase我不太了解,查了一下代码。
Sybase11Dialect extends SybaseDialect,SybaseDialect extends Dialect
分页是用的Dialect这里面的。不过从方言里的代码是这样的:
/**
	 * Add a <tt>LIMIT</tt> clause to the given SQL <tt>SELECT</tt>
	 *
	 * @return the modified SQL
	 */
	public String getLimitString(String querySelect, boolean hasOffset) {
		throw new UnsupportedOperationException( "paged queries not supported" );
	}

	public String getLimitString(String querySelect, int offset, int limit) {
		return getLimitString( querySelect, offset>0 );
	}

难道sybase分页的时候,抛出throw new UnsupportedOperationException( "paged queries not supported" )?

sybase这鬼东西,很不好用。没有分析函数top又不能在subquery中用(不会报错,但结果不对)。

sybase和MsSql有些渊源,貌似当初微软和sybase合作,后来出来了MsSql。jtds驱动支持这个两个数据库,sybase的hibernate方言里有这么一句注释:
/**
* An SQL dialect compatible with Sybase and MS SQL Server.
* @author Gavin King
*/

相关推荐

    STRUTS2+HIBERNATE详细的分页实现代码详细的分页实现代码

    根据提供的标题、描述、标签及部分内容,我们可以了解到这篇文章主要探讨的是如何在Struts2与Hibernate框架结合下...在实际项目中,可以根据具体需求进一步优化分页逻辑,例如增加缓存机制来减少数据库的访问次数等。

    hibernate分页Hibernate 分页的设计和编码

    Hibernate分页功能的设计与编码涉及多个方面,包括参数处理、数据库查询优化、前端显示逻辑等。正确实施分页不仅可以提升用户体验,还能显著提高应用程序的性能和响应速度,特别是在处理大数据量的情况下。理解并...

    spring-boot基于hibernate实现简单分页

    在本文中,我们将深入探讨如何使用Spring Boot与Hibernate框架实现简单的分页功能。Spring Boot以其简洁的配置和快速的应用开发而受到广泛欢迎,而Hibernate作为Java领域中的一个强大的ORM(对象关系映射)工具,...

    spring+struts2+hibernate整合实现分页

    在这个项目中,“spring+struts2+hibernate整合实现分页”是一个基础教程,旨在帮助初学者理解如何在SSH框架下实现数据的分页展示。下面将详细讲解这个主题中的关键知识点。 1. **Spring框架**:Spring是Java应用的...

    hibernate jsp 分页Demo

    9. **性能优化**: 分页时要考虑性能问题,如避免全表扫描,使用索引优化查询速度,以及合理设置缓存策略以减少数据库访问。 10. **数据安全**: 在处理用户输入以构造查询时,必须防止SQL注入攻击,可以通过预编译...

    Struts2 + Spring2 + Hibernate3经典分页(包含java及jsp源码)

    ### Struts2 + Spring2 + Hibernate3 经典分页技术解析 #### 一、概述 在企业级应用开发中,分页显示是常见的需求之一。...此外,合理的分页策略不仅可以提升用户体验,还能有效优化系统的性能表现。

    在JDBC,hibernate中实现分页

    为了优化用户体验并减轻服务器负担,分页技术被广泛应用于各种应用场景中。本文将深入探讨如何在Java开发环境中使用Hibernate框架以及JDBC结合SQL Server 2005来实现数据分页功能。 #### Hibernate中的分页实现 在...

    struts hibernate spring 分页ssh分页

    在实际应用中,SSH分页还可以结合缓存技术优化性能,比如使用Spring的Cache抽象来缓存部分分页结果。同时,为了提供更好的用户体验,分页组件通常会支持动态加载(懒加载),即只有当用户滚动到底部时才加载下一页...

    spring+hibernate 分页 +mysql

    在IT行业中,分页是数据库查询的一个重要特性,特别是在处理大量数据时,它可以显著提高系统的性能和用户体验。...在实际项目中,应根据具体需求调整分页策略,如缓存优化、避免全表扫描等,以达到最佳性能。

    用户Hibernate实现的一个分页

    本教程将详细讲解如何使用Hibernate实现分页功能,这对于处理大量数据的Web应用来说至关重要,因为它能够有效地减少数据库负载,提高用户体验。 一、Hibernate分页基础 1. Hibernate的Query和Criteria API都提供了...

    Struts+Hibernate实现分页

    本篇将介绍如何使用Struts和Hibernate框架来实现Web应用中的分页功能。 首先,Struts是一个基于MVC(Model-View-Controller)设计模式的Java Web框架,它负责处理用户请求并控制应用程序的流程。而Hibernate则是一...

    Hibernate+sqlserver2000分页

    在实际应用中,开发者需要根据业务需求选择合适的分页方法,并注意性能优化,如避免全表扫描,使用索引来加速查询等。理解并熟练掌握这些知识,对于提升Java Web应用的效率和用户体验至关重要。

    Struts + Hibernate 实现简单分页功能

    在Java Web开发中,Struts和Hibernate是两个非常重要的框架,它们分别负责表现层和持久层的管理。...在实际项目中,我们还需要考虑性能优化、异常处理、安全性等因素,使得分页功能更加完善和健壮。

    springMVC+spring+hibernate+jquery分页完整项目,完整代码

    5. 分页:在大型应用中,分页是必不可少的功能,可以有效管理大量数据并减轻服务器负担。在这个项目中,SpringMVC和Hibernate结合,实现了后端的分页处理,而jQuery则负责前端的分页展示和交互,用户可以轻松地浏览...

    Hibernate+struts分页

    本项目“Hibernate+Struts分页”是针对基于Sqlserver2000数据库的应用程序设计的,它整合了两个流行的Java开发框架——Hibernate(持久层框架)和Struts(MVC框架),以实现高效且用户友好的数据浏览功能。...

    struts2.0 + hibernate + oracle 分页问题

    Oracle还提供了其他高级特性,如游标、索引和物化视图,可以帮助优化分页查询的性能。 在实际开发中,结合Eclipse这样的集成开发环境,我们可以创建项目、配置Struts2和Hibernate的依赖,编写Action、DAO和实体类,...

    hibernate 优化

    《Hibernate优化深度解析》 在Java企业级开发中,Hibernate作为一款强大的对象关系映射(ORM)框架,极大地简化了数据库操作。然而,随着项目的规模扩大,如何进行有效的Hibernate优化,提升系统的性能,成为了...

    jsp+hibernate实现的分页,java代码

    5. **优化性能**:为了提高性能,可以考虑缓存部分数据,或者使用数据库的分页功能(如MySQL的`LIMIT`和`OFFSET`,Oracle的`ROWNUM`等)。另外,避免一次性加载过多数据,以免造成内存压力。 6. **错误处理和安全性...

    Struts和Hibernate分页及查询

    在实际开发中,还需要考虑性能优化,比如使用缓存技术(如Hibernate的二级缓存)、预编译查询(HQL的Named Queries)等。同时,安全性也是一个重要的话题,例如防止SQL注入,对用户输入进行验证等。 总结起来,...

    Hibernate分页查询原理解读

    此外,通过使用`Scrollable ResultSet`,Hibernate还可以进一步优化不支持特定分页语法的数据库的查询效率。总之,Hibernate为开发者提供了强大的分页查询功能,极大地简化了开发过程中的复杂性。

Global site tag (gtag.js) - Google Analytics