[转]修改 ibatis 分页机制(ORACLE物理分页)

时间:2020-11-29 20:11:19

  修改 ibatis 分页机制(ORACLE物理分页)

针对ibatis 分页机制的修改例子网上也有很多,本人也是参考后做的修改.如下:

 

1、首先增加配置文件

 

 

  1. <bean id="sqlExecutor" class="com.ibatis.sqlmap.engine.execution.XSqlExecutor">  
  2.         <property name="dialect">   
  3.             <bean class="com.ibatis.sqlmap.engine.imp.DialectImp" />   
  4.         </property>   
  5.         <property name="enableLimit">     
  6.             <value>true</value>     
  7.         </property>     
  8.     </bean>   

 

此类是继承地层分页代码从而实现修改SQL语句

 

 

  1. package com.ibatis.sqlmap.engine.execution;   
  2.   
  3.  import java.sql.Connection;    
  4.  import java.sql.SQLException;    
  5.   
  6.  import com.ibatis.sqlmap.engine.inter.Dialect;    
  7.  import com.ibatis.sqlmap.engine.mapping.statement.RowHandlerCallback;    
  8.  import com.ibatis.sqlmap.engine.scope.StatementScope;    
  9.   
  10.  /**   
  11.  *   
  12.  * @author chenyanji  
  13.  *   
  14.  * @project Vstsoft newsx code.  
  15.  * @datetime Mar 15, 2009 12:07:18 AM  
  16.  */  
  17.  public class XSqlExecutor extends SqlExecutor{    
  18.   
  19.        
  20.     private Dialect dialect;    
  21.   
  22.     private boolean enableLimit = true;    
  23.   
  24.     public Dialect getDialect() {    
  25.         return dialect;    
  26.     }   
  27.   
  28.     public void setDialect(Dialect dialect) {    
  29.         this.dialect = dialect;    
  30.     }   
  31.   
  32.     public boolean isEnableLimit() {    
  33.         return enableLimit;    
  34.     }   
  35.   
  36.     public void setEnableLimit(boolean enableLimit) {    
  37.         this.enableLimit = enableLimit;    
  38.     }   
  39.   
  40.     /**   
  41.      * 重写SqlExecutor.executeQuery方法 实现ORACLE的SQL物理分页 see OracleDialect  
  42.      */  
  43.     public void executeQuery(StatementScope request, Connection conn,    
  44.             String sql, Object[] parameters, int skipResults, int maxResults,    
  45.             RowHandlerCallback callback) throws SQLException {    
  46.         if (isLimit(sql, skipResults, maxResults)) {// 有分页信息、可物理分页SQL    
  47.             sql = dialect.getOracleLimit(sql, skipResults, maxResults);// 获得物理分页SQL    
  48.             skipResults = NO_SKIPPED_RESULTS;// 设置skipResults为SqlExecutor不分页    
  49.             maxResults = NO_MAXIMUM_RESULTS;// 设置maxResults为SqlExecutor不分页    
  50.         }   
  51.         super.executeQuery(request, conn, sql, parameters, skipResults,   
  52.                 maxResults, callback);// 使用不分页机制调用SqlExecutor查询方法   
  53.     }   
  54.     /**  
  55.      * 是否允许执行分页  
  56.      *   
  57.      * @param sql  
  58.      * @param skipResults  
  59.      * @param maxResults  
  60.      * @return  
  61.      */  
  62.     private boolean isLimit(String sql, int skipResults, int maxResults) {   
  63.         return (skipResults != NO_SKIPPED_RESULTS || maxResults != NO_MAXIMUM_RESULTS)   
  64.                 && enableLimit && isSelect(sql);   
  65.     }   
  66.   
  67.     /**  
  68.      * 是否可物理分页SQL  
  69.      *   
  70.      * @param sql  
  71.      * @return  
  72.      */  
  73.     private boolean isSelect(String sql) {   
  74.         if (sql.toLowerCase().indexOf("select") >= 0) {   
  75.             if (sql.toLowerCase().indexOf("rownum") >= 0) {   
  76.                 return false;   
  77.             }   
  78.             return true;   
  79.         }   
  80.         return false;   
  81.     }   
  82.   
  83. }  

 

修改语句接口

 

 

  1. package com.ibatis.sqlmap.engine.inter;   
  2.   
  3.  /**  
  4.  *   
  5.  * @author chenyanji  
  6.  *   
  7.  * @project Vstsoft newsx code.  
  8.  * @datetime Mar 15, 2009 12:07:24 AM  
  9.  */  
  10.  public interface Dialect {   
  11.   
  12.     /**  
  13.      * 获得分页SQL  
  14.      *   
  15.      * @param sql  
  16.      * @param offset  
  17.      * @param limit  
  18.      * @return  
  19.      */  
  20.     public String getOracleLimit(String sql, int offset, int limit);   
  21.   
  22. }  

 

实现类

 

 

  1. package com.ibatis.sqlmap.engine.imp;   
  2.   
  3.  import com.ibatis.sqlmap.engine.inter.Dialect;   
  4.   
  5.  /**  
  6.  *   
  7.  * @author chenyanji  
  8.  *   
  9.  * @project Vstsoft newsx code.  
  10.  * @datetime Mar 15, 2009 12:06:18 AM  
  11.  */  
  12.  public class DialectImp implements Dialect {   
  13.   
  14.     protected static final String SQL_END_DELIMITER = ";";   
  15.   
  16.     public String getOracleLimit(String sql, int offset, int limit) {   
  17.         if (offset == 1) {   
  18.             offset = 0;   
  19.         }   
  20.         StringBuffer pageStr = new StringBuffer();   
  21.         pageStr   
  22.                 .append("select * from ( select row_limit.*, rownum rownum_ from (");   
  23.         pageStr.append(this.trim(sql));   
  24.         pageStr.append(" ) row_limit where rownum <= ");   
  25.         pageStr.append(limit + offset);   
  26.         pageStr.append(" ) where rownum_ >");   
  27.         pageStr.append(offset);   
  28.         return pageStr.toString();   
  29.     }   
  30.   
  31.     /**  
  32.      * 去掉当前SQL 后分号  
  33.      *   
  34.      * @param sql  
  35.      * @return  
  36.      */  
  37.     private String trim(String sql) {   
  38.         sql = sql.trim();   
  39.         if (sql.endsWith(SQL_END_DELIMITER)) {   
  40.             sql = sql.substring(0, sql.length() - 1  
  41.                     - SQL_END_DELIMITER.length());   
  42.         }   
  43.         return sql;   
  44.     }   
  45. }  

 

2、增加继承类(ibatis调用地层类)

 

 

  1. package org.springside.core.dao.support;   
  2.   
  3.  import java.util.TimeZone;   
  4.   
  5.  import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;   
  6.  import org.springside.core.utils.ReflectUtil;   
  7.   
  8.  import com.ibatis.sqlmap.client.SqlMapClient;   
  9.  import com.ibatis.sqlmap.engine.execution.SqlExecutor;   
  10. import com.ibatis.sqlmap.engine.execution.XSqlExecutor;   
  11. import com.ibatis.sqlmap.engine.impl.ExtendedSqlMapClient;   
  12. import com.vstsoft.csi.core.log.Logger;   
  13.   
  14. /**  
  15.  *   
  16.  * @author chenyanji  
  17.  *   
  18.  * @project Vstsoft newsx code.  
  19.  * @datetime Mar 15, 2009 12:07:11 AM  
  20.  */  
  21. public abstract class XSqlMapClientDaoSupport extends SqlMapClientDaoSupport {   
  22.   
  23.     /**  
  24.      * chenyanji.修正JDK时区问题  
  25.      */  
  26.     public XSqlMapClientDaoSupport(){   
  27.         TimeZone.setDefault(TimeZone.getTimeZone("ETC/GMT-8"));    
  28.     }   
  29. //  protected  Logger logger = new Logger(this.getClass());   
  30.     private SqlExecutor sqlExecutor;   
  31.   
  32.     public SqlExecutor getSqlExecutor() {   
  33.         return sqlExecutor;   
  34.     }   
  35.   
  36.     public void setSqlExecutor(SqlExecutor sqlExecutor) {   
  37.         this.sqlExecutor = sqlExecutor;   
  38.     }   
  39.   
  40.     public void setEnableLimit(boolean enableLimit) {   
  41.         if (sqlExecutor instanceof XSqlExecutor) {   
  42.             ((XSqlExecutor) sqlExecutor).setEnableLimit(enableLimit);   
  43.         }   
  44.     }   
  45.   
  46.     /**  
  47.      * 将封装过的sqlExecutor对象SET给getSqlMapClientTemplate,从而改变继承sqlExecutor对象。  
  48.      *   
  49.      * @throws Exception  
  50.      */  
  51.     @SuppressWarnings("deprecation")   
  52.     public void initialized() {   
  53.         if (sqlExecutor != null) {   
  54.             SqlMapClient sqlMapClient = getSqlMapClientTemplate()   
  55.                     .getSqlMapClient();   
  56.             if (sqlMapClient instanceof ExtendedSqlMapClient) {   
  57.                 ReflectUtil.setFieldValue(((ExtendedSqlMapClient) sqlMapClient)   
  58.                         .getDelegate(), "sqlExecutor", SqlExecutor.class,   
  59.                         sqlExecutor);   
  60. //              logger.info(((ExtendedSqlMapClient) sqlMapClient).getDelegate()   
  61. //                      .getClass().getSimpleName()   
  62. //                      + "'s OracleLimit initialized.");   
  63.             } else {   
  64. //              logger.info("null OracleLimit initialized.");   
  65.             }   
  66.         }   
  67.     }   
  68. }  

 

本人使用springside作为执行数据库地层类,所以直接修改IBatisGenericDao继承为XSqlMapClientDaoSupport就可以

 

 

  1. public class IBatisGenericDao extends XSqlMapClientDaoSupport {  

 

 3、为所有SPRING配置文件增加初始化方法,作用是修改IBATIS地层SqlMapClientDaoSupport的实现类,使用我们自己写的类,以便方便控制于修改SQL

 

 

  1. <beans  default-init-method="initialized">  

 

 

  1. package org.springside.core.utils;   
  2.   
  3. import java.lang.reflect.Field;   
  4. import java.lang.reflect.Method;   
  5. import java.lang.reflect.Modifier;   
  6.   
  7. /**  
  8.  *   
  9.  * @author chenyanji  
  10.  *   
  11.  * @project Vstsoft newsx code.  
  12.  * @datetime Mar 15, 2009 12:07:33 AM  
  13.  */  
  14. public class ReflectUtil {   
  15.   
  16.     /**  
  17.      * 通过反射绕过java的访问控制,向对象中SET属性。  
  18.      * @param target  
  19.      * @param fname  
  20.      * @param ftype  
  21.      * @param fvalue  
  22.      */  
  23.     public static void setFieldValue(Object target, String fname, Class ftype, Object fvalue) {   
  24.         if (target == null || fname == null || "".equals(fname)   
  25.                 || (fvalue != null && !ftype.isAssignableFrom(fvalue.getClass()))) {   
  26.             return;   
  27.         }   
  28.         Class clazz = target.getClass();   
  29.         try {   
  30.             Method method = clazz.getDeclaredMethod("set" + Character.toUpperCase(fname.charAt(0))   
  31.                     + fname.substring(1), ftype);   
  32.             if (!Modifier.isPublic(method.getModifiers())) {   
  33.                 method.setAccessible(true);   
  34.             }   
  35.             method.invoke(target, fvalue);   
  36.   
  37.         } catch (Exception me) {   
  38.             try {   
  39.                 Field field = clazz.getDeclaredField(fname);   
  40.                 if (!Modifier.isPublic(field.getModifiers())) {   
  41.                     field.setAccessible(true);   
  42.                 }   
  43.                 field.set(target, fvalue);   
  44.             } catch (Exception fe) {   
  45.             }   
  46.         }   
  47.     }   
  48. }  

 

到此步,已经完成分页修改,和网上说的改法基本一致。

 

原理,初始化自己BEAN的同时也会初始化到父类父类。。。默认初始化方法initialized来进行修改IBATIS

 

经测试出现问题:已知一未初始化SQLMAP,使用分页进行初始化(SQLMAP会多映射一列,ORACLE3层分页导致),在进行不分页查询会出现少列错误,也就是说SQLMAP 多了一个映射列,查询语句无法进行完整匹配。SQLMAP唯一不足的是:SQLMAP映射可以不查询出的列少,但是绝对不允许多,现在我们使用分页初始化就多出一列。本人的解决办法是直接修改JAR包(未找到好的解决办法)

 


com.ibatis.sqlmap.engine.mapping.result.ResultMap 中 getPrimitiveResultMappingValue方法进行修改

 

增加过滤条件判断
 if(columnName.equals("ROWNUM_"))
                value = "";

 

(直接哪JAVA文件进行修改编译,然后直接替换包中CLASS,如需要可以所要)

 

最后提一点,也是容易忽略的地方,加了ORACLE分页后,原始SQL不能出现重复列、rownum,否则提示无法映射列错误!如 select * from tablea as a, tableb as b  where a.id=b.id 这样语句加上分页就会出现ID无法映射,两表都包含ID外层分页SQL无法解析ID是从那个表来的。

 

 因冲忙没有做详细讲解,如果质疑可以进一步讨论

 

本人最终实现完全修改JAR包,如果需要JAR包,请加入群(共享)本包只针对ORACLE进行修改,无其他扩展。