解決hibernate對Sql Server分頁慢的問題
阿新 • • 發佈:2017-08-09
不知道 cfg sdn track and web項目 方法封裝 its 增加用戶
一、hibernate分頁 hibernate對MsSql的偽分頁
分頁是web項目中比不可少的一個功能,數據量大的時候不能全部展示必然要用到分頁技術。相信大家對hibernate中的分頁都不陌生:
Java代碼
只要調用了這兩個方法並設置好參數,hibernate自動分頁完全屏蔽了底層數據庫分頁技術,這也是眾多開發者喜歡hibernate的原因之一。
項目開發中遇到一個奇怪的問題。數據庫采用的是Sql Server 2005,也設置了上面兩個參數,可是每次發送到數據庫端的SQL語句都是select top ....語句。即便是查詢第10w條,也只有一個select top 語句,不免引起對hibernate實現sql server分頁的懷疑。hibernate針對不同數據庫實現的分頁方法封裝在對應數據庫的方言裏,通過getLimitString方法轉化成對應數據庫的分頁算法。
以常見的Mysql數據庫的方言MySQLDialect為例:
Java代碼
采用了大家熟悉的mysql的limit進行分頁。
Oracle數據庫的方言Oracle9iDialect:
Java代碼
利用Oracle的rownum 結合三層嵌套查詢完成分頁。這個三層是Oracle最經典高效的分頁算法。
可是針對Sql Server的方言SQLServerDialect:
Java代碼
揉揉眼睛、再揉揉,沒錯,只出現了一個top語句。這就意味著如果查詢第10w頁的數據,需要把前10w頁數據全部提取出來。hibernate針對sql server的分頁是偽分頁,所以隨著數據量日益增加用戶抱怨系統速度慢,程序員抱怨hibernate性能低,dba抱怨開發人員sql功底太淺。
不知道hibernate開發組,出於什麽目前或情況沒有真正提供sql server的分頁技術,那我們自己來實現。
方言類:
Java代碼
第一頁采用top分頁,以後采用ROW_NUMBER分頁第一頁以上查詢要求sql中必須含有排序子句。
這只是在項目過程中采用的sql server遇到的該問題,如果使用mysql,oracle則不會遇到這個問題。
分頁是web項目中比不可少的一個功能,數據量大的時候不能全部展示必然要用到分頁技術。相信大家對hibernate中的分頁都不陌生:
Java代碼
- 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方法轉化成對應數據庫的分頁算法。
Java代碼
- public String getLimitString(String sql, boolean hasOffset) {
- return new StringBuffer( sql.length() + 20 )
- .append( sql )
- .append( hasOffset ? " limit ?, ?" : " limit ?" )
- .toString();
- }
采用了大家熟悉的mysql的limit進行分頁。
Oracle數據庫的方言Oracle9iDialect:
Java代碼
- 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:
Java代碼
- 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的分頁技術,那我們自己來實現。
方言類:
Java代碼
- 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類
<property name="dialect"> com.common.SQLServer2005Dialect </property> 或在與spring的集成配置文件中
<prop key="hibernate.dialect">com.common.SQLServer2005Dialect</prop>
在分頁時,就可以達到很好的效率了。
第一頁采用top分頁,以後采用ROW_NUMBER分頁第一頁以上查詢要求sql中必須含有排序子句。
這只是在項目過程中采用的sql server遇到的該問題,如果使用mysql,oracle則不會遇到這個問題。
解決hibernate對Sql Server分頁慢的問題