`
pengfeng
  • 浏览: 229322 次
  • 性别: Icon_minigender_1
  • 来自: 河南
社区版块
存档分类
最新评论

对IBatis分页的改进,使ibatis支持hibernate式的物理分页

阅读更多

      公司的大部分项目都开始使用IBatis作为O/R Mapping了,但是在使用的过程中也发现了很多不方便和存在争议的地方,其中一个不方便的地方就是分页,目前的处理方式都是在sqlMap中写针对特定数据库的物理分页Sql语句,对于oracle数据库都是在分页的sql语句里面加上三层嵌套的sql语句,想了很多办法,都没能很好的避免这个问题,无意间在javaeye上看到了《使ibatis支持hibernate式的物理分页》这篇文章,可点进去已经被删除了,幸好google了一下有很多人已经收藏了,这里转载一下,以便再找不到了.

 

转载地址:http://www.blogjava.net/libin2722/articles/192504.html

 

一直以来ibatis的分页都是通过滚动ResultSet实现的,应该算是逻辑分页吧。逻辑分页虽然能很干净地独立于特定数据库,但效率在多数情况下不及特定数据库支持的物理分页,而hibernate的分页则是直接组装sql,充分利用了特定数据库的分页机制,效率相对较高。本文讲述的就是如何在不重新编译ibatis源码的前提下,为ibatis引入hibernate式的物理分页机制。

基本思路就是找到ibatis执行sql的地方,截获sql并重新组装sql。通过分析ibatis源码知道,最终负责执行sql的类是 com.ibatis.sqlmap.engine.execution.SqlExecutor,此类没有实现任何接口,这多少有点遗憾,因为接口是相对稳定契约,非大的版本更新,接口一般是不会变的,而类就相对易变一些,所以这里的代码只能保证对当前版本(2.1.7)的ibatis有效。下面是 SqlExecutor执行查询的方法:

/**
   * Long form of the method to execute a query
   *
   * @param request - the request scope
   * @param conn - the database connection
   * @param sql - the SQL statement to execute
   * @param parameters - the parameters for the statement
   * @param skipResults - the number of results to skip
   * @param maxResults - the maximum number of results to return
   * @param callback - the row handler for the query
   *
   * @throws SQLException - if the query fails
   */
  public void executeQuery(RequestScope request, Connection conn, String sql, Object[] parameters,
                           int skipResults, int maxResults, RowHandlerCallback callback)
      throws SQLException {
    ErrorContext errorContext = request.getErrorContext();
    errorContext.setActivity("executing query");
    errorContext.setObjectId(sql);

    PreparedStatement ps = null;
    ResultSet rs = null;

    try {
      errorContext.setMoreInfo("Check the SQL Statement (preparation failed).");

      Integer rsType = request.getStatement().getResultSetType();
      if (rsType != null) {
        ps = conn.prepareStatement(sql, rsType.intValue(), ResultSet.CONCUR_READ_ONLY);
      } else {
        ps = conn.prepareStatement(sql);
      }

      Integer fetchSize = request.getStatement().getFetchSize();
      if (fetchSize != null) {
        ps.setFetchSize(fetchSize.intValue());
      }

      errorContext.setMoreInfo("Check the parameters (set parameters failed).");
      request.getParameterMap().setParameters(request, ps, parameters);

      errorContext.setMoreInfo("Check the statement (query failed).");

      ps.execute();
      rs = getFirstResultSet(ps);

      if (rs != null) {
        errorContext.setMoreInfo("Check the results (failed to retrieve results).");
        handleResults(request, rs, skipResults, maxResults, callback);
      }

      // clear out remaining results
      while (ps.getMoreResults());

    } finally {
      try {
        closeResultSet(rs);
      } finally {
        closeStatement(ps);
      }
    }

  }

 

其中handleResults(request, rs, skipResults, maxResults, callback)一句用于处理分页,其实此时查询已经执行完毕,可以不必关心handleResults方法,但为清楚起见,下面来看看 handleResults的实现:

private void handleResults(RequestScope request, ResultSet rs, int skipResults, int maxResults, RowHandlerCallback callback) throws SQLException {
    try {
      request.setResultSet(rs);
      ResultMap resultMap = request.getResultMap();
      if (resultMap != null) {
        // Skip Results
        if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
          if (skipResults > 0) {
            rs.absolute(skipResults);
          }
        } else {
          for (int i = 0; i < skipResults; i++) {
            if (!rs.next()) {
              break;
            }
          }
        }

        // Get Results
        int resultsFetched = 0;
        while ((maxResults == SqlExecutor.NO_MAXIMUM_RESULTS || resultsFetched < maxResults) && rs.next()) {
          Object[] columnValues = resultMap.resolveSubMap(request, rs).getResults(request, rs);
          callback.handleResultObject(request, columnValues, rs);
          resultsFetched++;
        }
      }
    } finally {
      request.setResultSet(null);
    }
  }

 

此处优先使用的是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的,不方便统一管理。

分享到:
评论
15 楼 wbbcz4426493 2016-06-24  
文章写的不错。继续加油。
14 楼 yyfmp3 2012-06-07  
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans  
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
           http://www.springframework.org/schema/context  
           http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 把标记了@Controller注解的类转换为bean  -->
<context:component-scan base-package="com.jd.app.cp.action" />
<!-- 文件上传 -->
<bean id="multipartReolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置上传文件大小的参数 -->
<property name="maxUploadSize" value="1000000" />
</bean>
<!-- 启动Spring MVC的注解功能,完成请求和注解POJO的映射 -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
<!-- Spring MVC -->
<!-- 对模型视图名称的解析,在请求时模型视图名称添加前后缀 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/view/" p:suffix=".jsp" />
    <bean id="multipartResolver"    
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver"    
          p:defaultEncoding="utf-8" />
         
<!-- ibtais 配置 -->

    <bean id="transactionManager" 
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
        <property name="dataSource" ref="dataSource" /> 
    </bean> 
 
    <bean id="dataSource" class="org.logicalcobwebs.proxool.ProxoolDataSource"> 
                <property name="driver">
                        <value>com.mysql.jdbc.Driver</value>
                </property>
                <property name="driverUrl">
                        <value>jdbc:mysql://localhost:3306/cp</value>
                </property>
                <property name="user" value="root"/>
                <property name="password" value="890929"/>
                <property name="alias" value="rss2data"/>
                <property name="maximumActiveTime" value="300000"/>
                <property name="prototypeCount" value="0"/>
                <property name="maximumConnectionCount" value="5"/>
                <property name="minimumConnectionCount" value="1"/>
                <property name="simultaneousBuildThrottle" value="5"/>
                <property name="houseKeepingTestSql" value="select CURRENT_DATE"/>
    </bean> 
   
    <bean id="sqlMapClient" 
        class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> 
        <property name="configLocation" 
            value="WEB-INF/sqlmap-config.xml" /> 
        <property name="dataSource" ref="dataSource" /> 
    </bean>
    
    <bean id="baseTxService" 
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" 
        abstract="true"> 
        <property name="transactionManager" ref="transactionManager" /> 
        <property name="proxyTargetClass" value="true" /> 
        <property name="transactionAttributes"> 
            <props> 
                <prop key="insert*">PROPAGATION_REQUIRED</prop> 
                <prop key="query*">readOnly</prop> 
                <prop key="get*">readOnly</prop> 
                <prop key="del*">PROPAGATION_REQUIRED</prop> 
                <prop key="update*">PROPAGATION_REQUIRED</prop> 
            </props> 
        </property> 
    </bean> 
   
    <bean id="sqlExecutor" class="com.jd.app.cp.dao.LimitSqlExecutor"> 
        <property name="dialect"> 
            <bean class="com.jd.app.cp.dialect.impl.MySQLDialect" /> 
        </property> 
    </bean>
      
    <bean id="basedao" abstract="true" class="com.jd.app.cp.dao.BaseDao" init-method="initialize"> 
        <property name="sqlMapClient"> 
            <ref bean="sqlMapClient" /> 
        </property> 
         <property name="sqlExecutor"> 
            <ref bean="sqlExecutor" /> 
        </property> 
    </bean>  
 
    <bean id="userdao" class="com.jd.app.cp.dao.impl.UserDaoImpl" parent="basedao" />
</beans>
这是我Spring 配置!
13 楼 yyfmp3 2012-06-07  
我按你的方法写完后,Spring提示注入失败!


2012-6-7 16:29:20 org.apache.catalina.core.AprLifecycleListener init
信息: The Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: H:\MyEclipse9.1\Common\binary\com.sun.java.jdk.win32.x86_64_1.6.0.013\bin;H:\MyEclipse9.1\Common\plugins\com.genuitec.eclipse.easie.tomcat.myeclipse_9.0.0.me201105051700\tomcat\bin
2012-6-7 16:29:20 org.apache.coyote.http11.Http11Protocol init
信息: Initializing Coyote HTTP/1.1 on http-8080
2012-6-7 16:29:20 org.apache.catalina.startup.Catalina load
信息: Initialization processed in 360 ms
2012-6-7 16:29:20 org.apache.catalina.core.StandardService start
信息: Starting service Catalina
2012-6-7 16:29:20 org.apache.catalina.core.StandardEngine start
信息: Starting Servlet Engine: Apache Tomcat/6.0.13
2012-6-7 16:29:21 org.apache.catalina.core.ApplicationContext log
信息: Initializing Spring root WebApplicationContext
log4j:WARN No appenders could be found for logger (org.springframework.web.context.ContextLoader).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
2012-6-7 16:29:22 org.apache.catalina.core.ApplicationContext log
信息: Initializing Spring FrameworkServlet 'spring'
2012-6-7 16:29:23 org.apache.catalina.core.ApplicationContext log
严重: StandardWrapper.Throwable
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userdao' defined in ServletContext resource [/WEB-INF/spring-servlet.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'sqlExecutor' of bean class [com.jd.app.cp.dao.impl.UserDaoImpl]: Bean property 'sqlExecutor' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1353)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1076)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:517)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:291)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:288)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:190)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:574)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:895)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:425)
at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:442)
at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:458)
at org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:339)
at org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:306)
at org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:127)
at javax.servlet.GenericServlet.init(GenericServlet.java:212)
at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1161)
at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:981)
at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:4042)
at org.apache.catalina.core.StandardContext.start(StandardContext.java:4348)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:791)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:771)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:525)
at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:920)
at org.apache.catalina.startup.HostConfig.deployDirectories(HostConfig.java:883)
at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:492)
at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1138)
at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:311)
at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:117)
at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1053)
at org.apache.catalina.core.StandardHost.start(StandardHost.java:719)
at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045)
at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:443)
at org.apache.catalina.core.StandardService.start(StandardService.java:516)
at org.apache.catalina.core.StandardServer.start(StandardServer.java:710)
at org.apache.catalina.startup.Catalina.start(Catalina.java:566)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:288)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:413)
Caused by: org.springframework.beans.NotWritablePropertyException: Invalid property 'sqlExecutor' of bean class [com.jd.app.cp.dao.impl.UserDaoImpl]: Bean property 'sqlExecutor' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
at org.springframework.beans.BeanWrapperImpl.setPropertyValue(BeanWrapperImpl.java:1016)
at org.springframework.beans.BeanWrapperImpl.setPropertyValue(BeanWrapperImpl.java:896)
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:76)
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:58)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1350)
... 42 more
2012-6-7 16:29:23 org.apache.catalina.core.StandardContext loadOnStartup
严重: Servlet /cp threw load() exception
org.springframework.beans.NotWritablePropertyException: Invalid property 'sqlExecutor' of bean class [com.jd.app.cp.dao.impl.UserDaoImpl]: Bean property 'sqlExecutor' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
at org.springframework.beans.BeanWrapperImpl.setPropertyValue(BeanWrapperImpl.java:1016)
at org.springframework.beans.BeanWrapperImpl.setPropertyValue(BeanWrapperImpl.java:896)
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:76)
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:58)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1350)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1076)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:517)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:291)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:288)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:190)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:574)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:895)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:425)
at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:442)
at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:458)
at org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:339)
at org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:306)
at org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:127)
at javax.servlet.GenericServlet.init(GenericServlet.java:212)
at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1161)
at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:981)
at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:4042)
at org.apache.catalina.core.StandardContext.start(StandardContext.java:4348)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:791)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:771)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:525)
at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:920)
at org.apache.catalina.startup.HostConfig.deployDirectories(HostConfig.java:883)
at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:492)
at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1138)
at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:311)
at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:117)
at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1053)
at org.apache.catalina.core.StandardHost.start(StandardHost.java:719)
at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045)
at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:443)
at org.apache.catalina.core.StandardService.start(StandardService.java:516)
at org.apache.catalina.core.StandardServer.start(StandardServer.java:710)
at org.apache.catalina.startup.Catalina.start(Catalina.java:566)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:288)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:413)
2012-6-7 16:29:23 org.apache.coyote.http11.Http11Protocol start
信息: Starting Coyote HTTP/1.1 on http-8080
2012-6-7 16:29:23 org.apache.jk.common.ChannelSocket init
信息: JK: ajp13 listening on /0.0.0.0:8009
2012-6-7 16:29:23 org.apache.jk.server.JkMain start
信息: Jk running ID=0 time=0/22  config=null
2012-6-7 16:29:23 org.apache.catalina.startup.Catalina start
信息: Server startup in 2424 ms
12 楼 yyfmp3 2012-06-07  
liuqq 写道
hellostory 写道
liuqq 写道
这篇文章被人多文章所追捧,但事实上,如果按照你这样重写了SqlExecutor 的实现类,的确可以解决物理分页问题,但是不能为了写分页而不全盘考虑iBatis的Cache设计。

如果你们的sqlMap配置了缓存,则这个物理分页就会有一个严重的Bug。
简单的说,如果有10页,在你点第一页的内容后,其他9页内容将会一致。

仔细看就一下MappedStatement代码和它的子类CachingStatement,就会发现问题。主要是这个CacheKey的组成是在你完成Sql拼接之前的,所以计算分页时,iBatis将计算出同一个CacheKey,则分页后显示内容总相同。

分页的思路除了最原始的sqlMap增加分页的动态参数办法外,应该着眼点在SqlMapExecutorDelegate类上,因为iBatis的问题,修改MappedStatement。逻辑分页参数传入时,改变Sql接口的实现类才能完善解决iBatis的分页缓存问题。

具体怎么操作?

使用原生的
list = ms.executeQueryForList(statementScope, trans, paramObject, skip, max);
方法去查询,核心是不直接修改执行的Sql语句,而是通过statementScope.setDynamicSql(sql);来改变,这样CachingStatement在计算CachingStatement时,会取得getDynamicSql()。
很不幸做起来没有这么简单,需要重写SqlMapExecutorDelegate这个执行代理。我会写一篇关于这个的文章,大家一起讨论。

按这样说,那该怎么办呢!
11 楼 liuqq 2012-03-08  
hellostory 写道
liuqq 写道
这篇文章被人多文章所追捧,但事实上,如果按照你这样重写了SqlExecutor 的实现类,的确可以解决物理分页问题,但是不能为了写分页而不全盘考虑iBatis的Cache设计。

如果你们的sqlMap配置了缓存,则这个物理分页就会有一个严重的Bug。
简单的说,如果有10页,在你点第一页的内容后,其他9页内容将会一致。

仔细看就一下MappedStatement代码和它的子类CachingStatement,就会发现问题。主要是这个CacheKey的组成是在你完成Sql拼接之前的,所以计算分页时,iBatis将计算出同一个CacheKey,则分页后显示内容总相同。

分页的思路除了最原始的sqlMap增加分页的动态参数办法外,应该着眼点在SqlMapExecutorDelegate类上,因为iBatis的问题,修改MappedStatement。逻辑分页参数传入时,改变Sql接口的实现类才能完善解决iBatis的分页缓存问题。

具体怎么操作?

使用原生的
list = ms.executeQueryForList(statementScope, trans, paramObject, skip, max);
方法去查询,核心是不直接修改执行的Sql语句,而是通过statementScope.setDynamicSql(sql);来改变,这样CachingStatement在计算CachingStatement时,会取得getDynamicSql()。
很不幸做起来没有这么简单,需要重写SqlMapExecutorDelegate这个执行代理。我会写一篇关于这个的文章,大家一起讨论。
10 楼 hellostory 2012-01-13  
liuqq 写道
这篇文章被人多文章所追捧,但事实上,如果按照你这样重写了SqlExecutor 的实现类,的确可以解决物理分页问题,但是不能为了写分页而不全盘考虑iBatis的Cache设计。

如果你们的sqlMap配置了缓存,则这个物理分页就会有一个严重的Bug。
简单的说,如果有10页,在你点第一页的内容后,其他9页内容将会一致。

仔细看就一下MappedStatement代码和它的子类CachingStatement,就会发现问题。主要是这个CacheKey的组成是在你完成Sql拼接之前的,所以计算分页时,iBatis将计算出同一个CacheKey,则分页后显示内容总相同。

分页的思路除了最原始的sqlMap增加分页的动态参数办法外,应该着眼点在SqlMapExecutorDelegate类上,因为iBatis的问题,修改MappedStatement。逻辑分页参数传入时,改变Sql接口的实现类才能完善解决iBatis的分页缓存问题。

具体怎么操作?
9 楼 liuqq 2012-01-13  
这篇文章被人多文章所追捧,但事实上,如果按照你这样重写了SqlExecutor 的实现类,的确可以解决物理分页问题,但是不能为了写分页而不全盘考虑iBatis的Cache设计。

如果你们的sqlMap配置了缓存,则这个物理分页就会有一个严重的Bug。
简单的说,如果有10页,在你点第一页的内容后,其他9页内容将会一致。

仔细看就一下MappedStatement代码和它的子类CachingStatement,就会发现问题。主要是这个CacheKey的组成是在你完成Sql拼接之前的,所以计算分页时,iBatis将计算出同一个CacheKey,则分页后显示内容总相同。

分页的思路除了最原始的sqlMap增加分页的动态参数办法外,应该着眼点在SqlMapExecutorDelegate类上,因为iBatis的问题,修改MappedStatement。逻辑分页参数传入时,改变Sql接口的实现类才能完善解决iBatis的分页缓存问题。
8 楼 yjcoffee 2009-11-12  
哈哈,发现和我做过相同事情的了,两年前我也做过类似的事情,除了支持自动分页还添加了自动生成count的statement,不过印象中继承的ibatis类比楼主要多。。。楼主写的代码也比较清晰,那段代码已经‘卖’给我上家公司了
7 楼 bomb2121 2009-07-02  
我用的是ibatis-2.3.3.720版本,RequestScope 类似乎已经不存在了,之后再其他地方看到似乎可以用StatementScope类来代替,但是还会出现类型转换异常,不知道LZ用的是哪个版本?是否最新版本的有较大改动。
6 楼 xintao222 2009-06-01  
记一下,后面研究研究
5 楼 jwinder 2009-03-10  
不错,喜欢我要收藏,到时研究一下!
4 楼 jerryscott2009 2008-12-07  
   大侠,如果现在使用的版本是IBATIS2.3.4应该如何办呢,如果使用自带的方法,如何做到绕过这种方式呢
3 楼 pengfeng 2008-10-31  
96sd2 写道

对了,你们现在支持mysql和oracle的翻页查询是怎么做的?

我们采用的正是文章中所写说的方式,只是又做了很多的改造,主要加入了一些自己定义的东西和权限等的控制
2 楼 96sd2 2008-10-24  
对了,你们现在支持mysql和oracle的翻页查询是怎么做的?
1 楼 96sd2 2008-06-17  
在这一点上,我们实现的是在XML配置中的SQL语句的上部加上一段<![CDATA[ $head$ ]]>,下部加上一段<![CDATA[ $foot$ ]]>,然后在DAO中根据不同的数据库类型来组合不同的SQL语句

相关推荐

    对IBatis分页的改进,使ibatis支持hibernate式的物理分页.doc

    一直以来ibatis的分页都是通过滚动ResultSet实现的,应该算是逻辑分页吧。逻辑分页虽然能很干净地独立于特定数据库,但...本文讲述的就是如何在不重新编译ibatis源码的前提下,为ibatis引入hibernate式的物理分页机制。

    spring-ibatis-ext-plugin.1.0.0 扩展ibaits原生SQL

    本文讲述的就是如何在不重新编译ibatis源码的前提下,为ibatis引入hibernate式的物理分页机制。 基本思路就是找到ibatis执行sql的地方,截获sql并重新组装sql。通过分析ibatis源码知道,最终负责执行sql的类是 ...

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

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

    ssh2+ibatis框架

    spring+hibernate+struts2+ibatis框架,同时实现了分页的方法,可以直接做为项目框架用。

    深入浅出Hibernate中文版 part1

    第10章 Hibernate益友——iBatis 10.1 Ibatis 概述 10.2 快速起步 10.3 IBatis 基础 10.4 iBatis高级特性 10.5 IBatis周边 第11章 工具卡片和软件 11.1 工具卡片 11.2 Eclipse安装和基本使用方法 11.3 快速...

    深入浅出Hibernate(PDF)第一部分

    直至Hibernate实战,重点讲述了Hibernate的基础语法、基础配置、O/R映射、数据关联、数据检索、HQL实用技术、自定义持久化实现、Hibernate回调与拦截、Hibernate分页等实用技术,Hibernate实战部分则用一个真实论坛...

    深入浅出Hibernate.pdf

    直至Hibernate实战,重点讲述了Hibernate的基础语法、基础配置、O/R映射、数据关联、数据检索、HQL实用技术、自定义持久化实现、Hibernate回调与拦截、Hibernate分页等实用技术,Hibernate实战部分则用一个真实论坛...

    深入浅出Hibernate(PDF)第二部分

    直至Hibernate实战,重点讲述了Hibernate的基础语法、基础配置、O/R映射、数据关联、数据检索、HQL实用技术、自定义持久化实现、Hibernate回调与拦截、Hibernate分页等实用技术,Hibernate实战部分则用一个真实论坛...

    中文 深入浅出Hibernate

    直至Hibernate实战,重点讲述了Hibernate的基础语法、基础配置、O/R映射、数据关联、数据检索、HQL实用技术、自定义持久化实现、Hibernate回调与拦截、Hibernate分页等实用技术,Hibernate实战部分则用一个真实论坛...

    深入浅出hibernate(PDF)第三部分

    直至Hibernate实战,重点讲述了Hibernate的基础语法、基础配置、O/R映射、数据关联、数据检索、HQL实用技术、自定义持久化实现、Hibernate回调与拦截、Hibernate分页等实用技术,Hibernate实战部分则用一个真实论坛...

    深入浅出Hibernate

    本书有丰富的附录部,在附录中讲述了Hibernate常用的映射配置,Hibernate工具、XDoclet模板配置以及Hibernate的益友iBatis用法,还以卡片的形式列出了本书中所用的工具及软件,附录最后一部分是“快速启动代码”,供...

    深入浅出Hibernate中文版 part2

    第10章 Hibernate益友——iBatis 10.1 Ibatis 概述 10.2 快速起步 10.3 IBatis 基础 10.4 iBatis高级特性 10.5 IBatis周边 第11章 工具卡片和软件 11.1 工具卡片 11.2 Eclipse安装和基本使用方法 11.3 快速...

    深入浅出Hibernate2

    直至Hibernate实战,重点讲述了Hibernate的基础语法、基础配置、O/R映射、数据关联、数据检索、HQL实用技术、自定义持久化实现、Hibernate回调与拦截、Hibernate分页等实用技术,Hibernate实战部分则用一个真实论坛...

    \深入浅出Hibernate

    直至Hibernate实战,重点讲述了Hibernate的基础语法、基础配置、O/R映射、数据关联、数据检索、HQL实用技术、自定义持久化实现、Hibernate回调与拦截、Hibernate分页等实用技术,Hibernate实战部分则用一个真实论坛...

    iBatis条件查询

    iBatis做的按条件查询,没关联其他表,内带Mysql,适用于小型项目,hibernate是全自动化,如有朋友想学习下半自动化持久层框架,那就来下载这个看看吧。。

    《深入浅出Hibernate》1

    直至Hibernate实战,重点讲述了Hibernate的基础语法、基础配置、O/R映射、数据关联、数据检索、HQL实用技术、自定义持久化实现、Hibernate回调与拦截、Hibernate分页等实用技术,Hibernate实战部分则用一个真实论坛...

    Struts2SpringHibernateIBatisPageCount

    以mysql数据库为基础做的整合例子,Struts2 Spring Hibernate IBatis 四个框架集成,再集成了hibernate分页技术,Ibatis分页技术。非常好用。创建数据库和下载jar包,直接导入立即可用,对学者学习是一个很好的例子...

    夏昕.深入浅出Hibernate

    本书有丰富的附录部,在附录中讲述了Hibernate常用的映射配置,Hibernate工具、XDoclet模板配置以及Hibernate的益友iBatis用法,还以卡片的形式列出了本书中所用的工具及软件,附录最后一部分是“快速启动代码”,供...

Global site tag (gtag.js) - Google Analytics