`
pengfeng
  • 浏览: 231669 次
  • 性别: 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源码的前提下,让iBatis支持类似Hibernate的物理分页,提高查询效率,尤其在大数据量的场景下,这种优化尤为关键。不过,需要注意的是,不同的数据库系统对物理分页...

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

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

    ssh2+ibatis框架

    SSH2+IBATIS框架是Java开发中常用的一套企业级应用开发框架组合,它将Spring、Hibernate、Struts2和iBatis等组件融合在一起,以提高开发效率和实现松耦合。以下是对这些框架及其整合方式的详细说明: **Spring框架*...

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

    本项目是一个基于SpringMVC、Spring、Hibernate和jQuery的完整分页应用,涵盖了后端服务、数据持久化、前端交互等多个重要技术领域。下面将详细解释这些技术及其在项目中的应用。 1. SpringMVC:SpringMVC是Spring...

    mysql,jdbc详解,与ibatis对比。批量查询,分页处理。

    ### MySQL、JDBC详解及与iBatis对比 ...通过对MySQL的基本操作、JDBC的工作原理以及iBatis与JDBC的对比分析,我们可以更全面地理解数据库编程的关键概念和技术要点。希望本文能够帮助开发者们更加熟练地掌握这些技能。

    Ibatis中文版教程

    除了基本的查询,iBatis还支持动态SQL、批量操作、分页等功能,这些都是在实际项目中非常实用的能力。例如,动态SQL允许开发者根据不同的条件构建SQL语句,而批量操作则可以提高数据处理的效率。 ### 四、总结 ...

    ibatis-2.3.4.zip

    Ibatis的核心理念是“SQL就是SQL”,它并不像Hibernate那样尝试完全对象化数据库,而是将数据访问逻辑保留在SQL语句中,通过XML配置文件或注解来绑定SQL与Java对象。这使得开发人员可以充分利用SQL的灵活性,同时...

    iBatis简明教程及快速入门

    - **配置简单**:最新的iBatis版本(例如2.0)改进了XML配置文件,使其更加直观易懂,即使是没有深入学习过iBatis的新手也能够快速理解配置文件的结构和意义。 #### 三、环境搭建与基本配置 1. **安装iBatis**:...

    详细介绍Ibatis的用法

    与其他数据库持久层框架如JDO、Hibernate相比,Ibatis的最大优势在于其简洁性和易学性。只需掌握JavaBean、XML和SQL的基础知识,开发者就能有效地利用Ibatis发挥出SQL的强大功能。 #### 安装与配置 - **JAR文件和...

    ibatisDemo.rar

    与Hibernate相比,Ibatis更注重SQL的自由度,适合对SQL有深度需求的项目。而Hibernate则以全自动化为特点,通过ORM机制简化了数据库操作,但在大数据量和复杂查询时可能效率较低。选择Ibatis还是Hibernate,取决于...

    spring mvc+ibatis+spring注解

    在Ibatis中,可以通过动态SQL来实现排序(ORDER BY)和分页(LIMIT 或 ROWNUM),而在控制器层,Spring MVC 可以处理请求参数,传递分页和排序条件到服务层,从而返回对应的查询结果。 "新增、修改、删除"是CRUD...

    分页的源码

    在IT行业中,分页是数据库查询的一个重要特性,特别是在数据量庞大的情况下,它能帮助...在 chap_ibatis 压缩包中,可能包含了相关的Ibatis分页源码示例,你可以进一步研究其中的细节,加深对Ibatis分页机制的理解。

    ibatis学习笔记.txt

    - 对于那些对性能有较高要求的应用场景,iBatis 的灵活性和可控性使其成为更好的选择。 **5.2 复杂查询** - 当应用中包含大量复杂的 SQL 查询时,iBatis 的手动 SQL 控制能够更好地满足需求。 **5.3 简单查询** - ...

    Ibatis视频下载

    Ibatis是由Apache软件基金会维护的一个开源项目,它的核心功能是将SQL语句与Java代码分离,提供灵活的数据访问层,使数据库操作更加简单、高效。 在视频教程中,你可能会学到以下关键知识点: 1. **Ibatis简介**:...

    IBATIS.pdf

    虽然与Hibernate相比,iBATIS可能在自动化程度和缓存管理上略显不足,但对于那些需要对SQL查询有精细化控制的应用场景,iBATIS无疑是更好的选择。开发者应根据项目的具体需求和团队的技术栈来决定采用哪种框架,以...

    iBatis条件查询

    iBatis是一个轻量级的Java持久层框架,它与Hibernate相比,更加灵活,适合于对数据库操作有自定义需求的项目。在本资源中,"iBatis条件查询"着重展示了如何根据业务需求定制SQL语句进行数据检索,尤其在不涉及复杂...

    abc.rar_ABC_ibatis

    此外,由于Hibernate的自动管理特性,对于某些特定的数据库优化操作,如分页查询、批处理更新等,可能不如iBatis那么直接和高效。 其次,iBatis的学习曲线相对较平缓,因为它允许开发者保留对SQL的直接控制。而...

    ibatis 开发指南.pdf

    8. 分页查询:iBatis支持通过设置参数实现分页查询,这对大数据量的展示非常有用。 9. 缓存机制:iBatis提供了本地缓存和二级缓存机制,可以有效减少数据库访问,提高系统性能。开发者可以根据需求选择启用并配置...

    利用Mybatis的动态SQL实现物理分页.pdf

    【标题】:“利用Mybatis的动态SQL实现物理分页” 【描述】:本文主要探讨了在实际项目中如何利用Mybatis的动态SQL功能来解决大数据量下的物理分页问题,以避免内存溢出。 【标签】:“SQL 数据库 数据处理 参考...

Global site tag (gtag.js) - Google Analytics