此 处优先使用的是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的,不方便统一管理。
分享到:
相关推荐
公司的大部分项目都开始使用IBatis作为O/R Mapping了,但是在使用的过程中也发现了很多不方便和存在...想了很多办法,都没能很好的避免这个问题,无意间在javaeye上看到了《使ibatis支持hibernate式的物理分页》这篇文章,
总之,通过以上方法,我们可以实现在不修改iBatis源码的前提下,让iBatis支持类似Hibernate的物理分页,提高查询效率,尤其在大数据量的场景下,这种优化尤为关键。不过,需要注意的是,不同的数据库系统对物理分页...
本文讲述的就是如何在不重新编译ibatis源码的前提下,为ibatis引入hibernate式的物理分页机制。 基本思路就是找到ibatis执行sql的地方,截获sql并重新组装sql。通过分析ibatis源码知道,最终负责执行sql的类是 ...
【标题】:“利用Mybatis的动态SQL实现物理分页” 【描述】:本文主要探讨了在实际项目中如何利用Mybatis的动态SQL功能来解决大数据量下的物理分页问题,以避免内存溢出。 【标签】:“SQL 数据库 数据处理 参考...
(5)在ibatis中,想做物理分页,你必须在写一个列表sql,还要写一个90%一样的总页数的sql,不仅如此,你还要在列表sql外包上一层分页sql代码,你不觉得麻烦吗?但Hitis,直接返回给你实际执行的sql和paramters数组,...
(5)在ibatis中,想做物理分页,你必须在写一个列表sql,还要写一个90%一样的总页数的sql,不仅如此,你还要在列表sql外包上一层分页sql代码,你不觉得麻烦吗?但Hitis,直接返回给你实际执行的sql和paramters数组,...
(5)在ibatis中,想做物理分页,你必须在写一个列表sql,还要写一个90%一样的总页数的sql,不仅如此,你还要在列表sql外包上一层分页sql代码,你不觉得麻烦吗?但Hitis,直接返回给你实际执行的sql和paramters数组,...
(5)在ibatis中,想做物理分页,你必须在写一个列表sql,还要写一个90%一样的总页数的sql,不仅如此,你还要在列表sql外包上一层分页sql代码,你不觉得麻烦吗?但Hitis,直接返回给你实际执行的sql和paramters数组,...
(5)在ibatis中,想做物理分页,你必须在写一个列表sql,还要写一个90%一样的总页数的sql,不仅如此,你还要在列表sql外包上一层分页sql代码,你不觉得麻烦吗?但Hitis,直接返回给你实际执行的sql和paramters数组,...
(5)在ibatis中,想做物理分页,你必须在写一个列表sql,还要写一个90%一样的总页数的sql,不仅如此,你还要在列表sql外包上一层分页sql代码,你不觉得麻烦吗?但Hitis,直接返回给你实际执行的sql和paramters数组,...
Hibernate、Ibatis、Jdbc三者的区别:Hibernate和Ibatis都是JDBC的封装框架,但Hibernate是全自动的ORM映射,而Ibatis是半自动的。 Hibernate的运行原理:Hibernate通过映射关系将Java对象和数据库表进行映射。 ...
以前曾经用过ibatis,这是mybatis的前身,当时在做项目时,感觉很不错,比hibernate灵活。性能也比hibernate好。而且也比较轻量级,因为当时在项目中,没来的及做很很多笔记。后来项目结束了,我也没写总结文档。已经...
##### Hibernate、Ibatis、Jdbc三者的区别 - **Hibernate**:全ORM框架,简化了数据持久化操作。 - **iBatis**(MyBatis):半ORM框架,需要手动编写SQL语句。 - **JDBC**:直接使用SQL语句进行数据库操作。 ##### ...
##### Hibernate、Ibatis、Jdbc三者的区别 - **Hibernate**:全ORM解决方案,自动管理对象的生命周期。 - **MyBatis (原Ibatis)**:半ORM框架,提供动态SQL支持。 - **JDBC**:直接与数据库交互,灵活性高但编码...