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