使ibatis支持hibernate式的物理分页(2)

此 处优先使用的是ResultSet的absolute方法定位记录,是否支持absolute取决于具体数据库驱动,但一般当前版本的数据库都支持该方 法,如果不支持则逐条跳过前面的记录。由此可以看出如果数据库支持absolute,则ibatis内置的分页策略与特定数据库的物理分页效率差距就在于 物理分页查询与不分页查询在数据库中的执行效率的差距了。因为查询执行后读取数据前数据库并未把结果全部返回到内存,所以本身在存储占用上应该差距不大, 如果都使用索引,估计执行速度也差不太多。

继续我们的话题。其实只要在executeQuery执行前组装sql,然后将其传给 executeQuery,并告诉handleResults我们不需要逻辑分页即可。拦截executeQuery可以采用aop动态实现,也可直接继 承SqlExecutor覆盖executeQuery来静态地实现,相比之下后者要简单许多,而且由于SqlExecutor没有实现任何接口,比较易 变,动态拦截反到增加了维护的工作量,所以我们下面来覆盖executeQuery:

package  com.aladdin.dao.ibatis.ext;

import  java.sql.Connection;
import  java.sql.SQLException;

import  org.apache.commons.logging.Log;
import  org.apache.commons.logging.LogFactory;

import  com.aladdin.dao.dialect.Dialect;
import  com.ibatis.sqlmap.engine.execution.SqlExecutor;
import  com.ibatis.sqlmap.engine.mapping.statement.RowHandlerCallback;
import  com.ibatis.sqlmap.engine.scope.RequestScope;

public   class  LimitSqlExecutor  extends  SqlExecutor {

    
private   static   final  Log logger  =  LogFactory.getLog(LimitSqlExecutor. class );
    
    
private  Dialect dialect;

    
private   boolean  enableLimit  =   true ;

    
public  Dialect getDialect() {
        
return  dialect;
    }

    
public   void  setDialect(Dialect dialect) {
        
this .dialect  =  dialect;
    }

    
public   boolean  isEnableLimit() {
        
return  enableLimit;
    }

    
public   void  setEnableLimit( boolean  enableLimit) {
        
this .enableLimit  =  enableLimit;
    }

    @Override
    
public   void  executeQuery(RequestScope request, Connection conn, String sql,
            Object[] parameters, 
int  skipResults,  int  maxResults,
            RowHandlerCallback callback) 
throws  SQLException {
        
if  ((skipResults  !=  NO_SKIPPED_RESULTS  ||  maxResults  !=  NO_MAXIMUM_RESULTS)
                
&&  supportsLimit()) {
            sql 
=  dialect.getLimitString(sql, skipResults, maxResults);
            
if (logger.isDebugEnabled()){
                logger.debug(sql);
            }
            skipResults 
=  NO_SKIPPED_RESULTS;
            maxResults 
=  NO_MAXIMUM_RESULTS;            
        }
        
super .executeQuery(request, conn, sql, parameters, skipResults,
                maxResults, callback);
    }

    
public   boolean  supportsLimit() {
        
if  (enableLimit  &&  dialect  !=   null ) {
            
return  dialect.supportsLimit();
        }
        
return   false ;
    }

}

其中:

skipResults  =  NO_SKIPPED_RESULTS;
maxResults 
=  NO_MAXIMUM_RESULTS;

告诉handleResults不分页(我们组装的sql已经使查询结果是分页后的结果了),此处引入了类似hibenate中的数据库方言接口Dialect,其代码如下:

package  com.aladdin.dao.dialect;

public   interface  Dialect {
    
    
public   boolean  supportsLimit();

    
public  String getLimitString(String sql,  boolean  hasOffset);

    
public  String getLimitString(String sql,  int  offset,  int  limit);
}

 

下面为Dialect接口的MySQL实现: 

package  com.aladdin.dao.dialect;

public   class  MySQLDialect  implements  Dialect {

    
protected   static   final  String SQL_END_DELIMITER  =   " ; " ;

    
public  String getLimitString(String sql,  boolean  hasOffset) {
        
return   new  StringBuffer(sql.length()  +   20 ).append(trim(sql)).append(
                hasOffset 
?   "  limit ?,? "  :  "  limit ? " )
                .append(SQL_END_DELIMITER).toString();
    }

    
public  String getLimitString(String sql,  int  offset,  int  limit) {
        sql 
=  trim(sql);
        StringBuffer sb 
=   new  StringBuffer(sql.length()  +   20 );
        sb.append(sql);
        
if  (offset  >   0 ) {
            sb.append(
"  limit  " ).append(offset).append( ' , ' ).append(limit)
                    .append(SQL_END_DELIMITER);
        } 
else  {
            sb.append(
"  limit  " ).append(limit).append(SQL_END_DELIMITER);
        }
        
return  sb.toString();
    }

    
public   boolean  supportsLimit() {
        
return   true ;
    }

    
private  String trim(String sql) {
        sql 
=  sql.trim();
        
if  (sql.endsWith(SQL_END_DELIMITER)) {
            sql 
=  sql.substring( 0 , sql.length()  -   1
                    
-  SQL_END_DELIMITER.length());
        }
        
return  sql;
    }

}

接下来的工作就是把LimitSqlExecutor注入ibatis中。我们是通过spring来使用ibatis的,所以在我们的dao基类中执行注入,代码如下:

package  com.aladdin.dao.ibatis;

import  java.io.Serializable;
import  java.util.List;

import  org.springframework.orm.ObjectRetrievalFailureException;
import  org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;

import  com.aladdin.dao.ibatis.ext.LimitSqlExecutor;
import  com.aladdin.domain.BaseObject;
import  com.aladdin.util.ReflectUtil;
import  com.ibatis.sqlmap.client.SqlMapClient;
import  com.ibatis.sqlmap.engine.execution.SqlExecutor;
import  com.ibatis.sqlmap.engine.impl.ExtendedSqlMapClient;

public   abstract   class  BaseDaoiBatis  extends  SqlMapClientDaoSupport {

    
private  SqlExecutor sqlExecutor;

    
public  SqlExecutor getSqlExecutor() {
        
return  sqlExecutor;
    }

    
public   void  setSqlExecutor(SqlExecutor sqlExecutor) {
        
this .sqlExecutor  =  sqlExecutor;
    }

    
public   void  setEnableLimit( boolean  enableLimit) {
        
if  (sqlExecutor  instanceof  LimitSqlExecutor) {
            ((LimitSqlExecutor) sqlExecutor).setEnableLimit(enableLimit);
        }
    }

    
public   void  initialize()  throws  Exception {
        
if  (sqlExecutor  !=   null ) {
            SqlMapClient sqlMapClient 
=  getSqlMapClientTemplate()
                    .getSqlMapClient();
            
if  (sqlMapClient  instanceof  ExtendedSqlMapClient) {
                ReflectUtil.setFieldValue(((ExtendedSqlMapClient) sqlMapClient)
                        .getDelegate(), 
" sqlExecutor " , SqlExecutor. class ,
                        sqlExecutor);
            }
        }
    }

    ...

}

 

其 中的initialize方法执行注入,稍后会看到此方法在spring Beans 配置中指定为init-method。由于sqlExecutor是 com.ibatis.sqlmap.engine.impl.ExtendedSqlMapClient的私有成员,且没有公开的set方法,所以此处 通过反射绕过java的访问控制,下面是ReflectUtil的实现代码:

package  com.aladdin.util;

import  java.lang.reflect.Field;
import  java.lang.reflect.Method;
import  java.lang.reflect.Modifier;

import  org.apache.commons.logging.Log;
import  org.apache.commons.logging.LogFactory;

public   class  ReflectUtil {

    
private   static   final  Log logger  =  LogFactory.getLog(ReflectUtil. class );

    
public   static   void  setFieldValue(Object target, String fname, Class ftype,
            Object fvalue) {
        
if  (target  ==   null
                
||  fname  ==   null
                
||   "" .equals(fname)
                
||  (fvalue  !=   null   &&   ! ftype.isAssignableFrom(fvalue.getClass()))) {
            
return ;
        }
        Class clazz 
=  target.getClass();
        
try  {
            Method method 
=  clazz.getDeclaredMethod( " set "
                    
+  Character.toUpperCase(fname.charAt( 0 ))
                    
+  fname.substring( 1 ), ftype);
            
if  ( ! Modifier.isPublic(method.getModifiers())) {
                method.setAccessible(
true );
            }
            method.invoke(target, fvalue);

        } 
catch  (Exception me) {
            
if  (logger.isDebugEnabled()) {
                logger.debug(me);
            }
            
try  {
                Field field 
=  clazz.getDeclaredField(fname);
                
if  ( ! Modifier.isPublic(field.getModifiers())) {
                    field.setAccessible(
true );
                }
                field.set(target, fvalue);
            } 
catch  (Exception fe) {
                
if  (logger.isDebugEnabled()) {
                    logger.debug(fe);
                }
            }
        }
    }
}

 

到此剩下的就是通过Spring将sqlExecutor注入BaseDaoiBatis中了,下面是Spring Beans配置文件:

<? xml version="1.0" encoding="UTF-8" ?>
<! DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd"
>

< beans >
    
<!--  Transaction manager for a single JDBC DataSource  -->
    
< bean  id ="transactionManager"  class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
        
< property  name ="dataSource" >
            
< ref  bean ="dataSource"   />
        
</ property >
    
</ bean >
    
    
<!--  SqlMap setup for iBATIS Database Layer  -->
    
< bean  id ="sqlMapClient"  class ="org.springframework.orm.ibatis.SqlMapClientFactoryBean" >
        
< property  name ="configLocation" >
            
< value > classpath:/com/aladdin/dao/ibatis/sql-map-config.xml </ value >
        
</ property >
        
< property  name ="dataSource" >
            
< ref  bean ="dataSource"   />
        
</ property >
    
</ bean >

    
< bean  id ="sqlExecutor"  class ="com.aladdin.dao.ibatis.ext.LimitSqlExecutor" >
        
< property  name ="dialect" >
            
< bean  class ="com.aladdin.dao.dialect.MySQLDialect"   />
        
</ property >
    
</ bean >
    
    
< bean  id ="baseDao"  abstract ="true"  class ="com.aladdin.dao.ibatis.BaseDaoiBatis"  init-method ="initialize" >
        
< property  name ="dataSource" >
            
< ref  bean ="dataSource"   />
        
</ property >
        
< property  name ="sqlMapClient" >
            
< ref  bean ="sqlMapClient"   />
        
</ property >
        
< property  name ="sqlExecutor" >
            
< ref  bean ="sqlExecutor"   />
        
</ property >  
    
</ bean >  
    
    
< bean  id ="userDao"  class ="com.aladdin.dao.ibatis.UserDaoiBatis"  parent ="baseDao"   />  

    
< bean  id ="roleDao"  class ="com.aladdin.dao.ibatis.RoleDaoiBatis"  parent ="baseDao"   />
    
    
< bean  id ="resourceDao"  class ="com.aladdin.dao.ibatis.ResourceDaoiBatis"  parent ="baseDao"   />
    
</ beans >

 

此后就可以通过调用org.springframework.orm.ibatis.SqlMapClientTemplate的

public List queryForList(final String statementName, final Object parameterObject, final int skipResults, final int maxResults)   throws DataAccessException

public PaginatedList queryForPaginatedList(final String statementName, final Object parameterObject, final int pageSize)   throws DataAccessException

得到分页结果了。建议使用第一个方法,第二个方法返回的是PaginatedList,虽然使用简单,但是其获得指定页的数据是跨过我们的dao直接访问ibatis的,不方便统一管理。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值