(注:以下代码是基于ibatis3 beta4的扩展,ibatis3正式版如果实现改变,将会继续跟进修改)
iBatis3默认使用的分页是基于游标的分页,而这种分页在不同的数据库上性能差异不一致,最好的办法当然是使用类似hibernate的基于方言(Dialect)的物理分页功能。
iBatis3现在提供插件功能,通过插件我们可以编写自己的拦截器来拦截iBatis3的主要执行方法来完成相关功能的扩展。
能够拦截的的类如下:
Java代码
Executor
(update,query,flushStatements,commit,rollback,getTransaction,close,isClosed)
ParameterHandler
(getParameterObject,setParameters)
ResultSetHandler
(handleResultSets,handleOutputParameters)
StatementHandler
(prepare,parameterize,batch,update,query)
但此次我们感兴趣的是Executor.query()方法,查询方法最终都是执行该方法。
该方法完整是:
Java代码
Executor.query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)throwsSQLException;
分页方言的基本实现:
以Mysql数据库示例,即有一个方法 query(sql,offset,limit);
Java代码
query("select * from user",5,10);
经过我们的拦截器拦截处理,变成
Java代码
query("select * from user limit 5,10",,);
1.拦截类实现:
Java代码
@Intercepts({@Signature(
type= Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
publicclassOffsetLimitInterceptorimplementsInterceptor{
staticintMAPPED_STATEMENT_INDEX =;
staticintPARAMETER_INDEX =1;
staticintROWBOUNDS_INDEX =2;
staticintRESULT_HANDLER_INDEX =3;
Dialect dialect;
publicObject intercept(Invocation invocation)throwsThrowable {
processIntercept(invocation.getArgs());
returninvocation.proceed();
}
voidprocessIntercept(finalObject[] queryArgs) {
//queryArgs = query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
MappedStatement ms = (MappedStatement)queryArgs[MAPPED_STATEMENT_INDEX];
Object parameter = queryArgs[PARAMETER_INDEX];
finalRowBounds rowBounds = (RowBounds)queryArgs[ROWBOUNDS_INDEX];
intoffset = rowBounds.getOffset();
intlimit = rowBounds.getLimit();
if(dialect.supportsLimit() && (offset != RowBounds.NO_ROW_OFFSET || limit != RowBounds.NO_ROW_LIMIT)) {
BoundSql boundSql = ms.getBoundSql(parameter);
String sql = boundSql.getSql().trim();
if(dialect.supportsLimitOffset()) {
sql = dialect.getLimitString(sql, offset, limit);
offset = RowBounds.NO_ROW_OFFSET;
} else{
sql = dialect.getLimitString(sql, , limit);
}
limit = RowBounds.NO_ROW_LIMIT;
queryArgs[ROWBOUNDS_INDEX] = newRowBounds(offset,limit);
BoundSql newBoundSql = newBoundSql(sql, boundSql.getParameterMappings(), boundSql.getParameterObject());
MappedStatement newMs = copyFromMappedStatement(ms, newBoundSqlSqlSource(newBoundSql));
queryArgs[MAPPED_STATEMENT_INDEX] = newMs;
}
}
privateMappedStatement copyFromMappedStatement(MappedStatement ms,SqlSource newSqlSource) {
Builder builder = newMappedStatement.Builder(ms.getConfiguration(),ms.getId(),newSqlSource,ms.getSqlCommandType());
builder.resource(ms.getResource());
builder.fetchSize(ms.getFetchSize());
builder.statementType(ms.getStatementType());
builder.keyGenerator(ms.getKeyGenerator());
builder.keyProperty(ms.getKeyProperty());
builder.timeout(ms.getTimeout());
builder.parameterMap(ms.getParameterMap());
builder.resultMaps(ms.getResultMaps());
builder.cache(ms.getCache());
MappedStatement newMs = builder.build();
returnnewMs;
}
publicObject plugin(Object target) {
returnPlugin.wrap(target,this);
}
publicvoidsetProperties(Properties properties) {
String dialectClass = newPropertiesHelper(properties).getRequiredString("dialectClass");
try{
dialect = (Dialect)Class.forName(dialectClass).newInstance();
} catch(Exception e) {
thrownewRuntimeException("cannot create dialect instance by dialectClass:"+dialectClass,e);
}
System.out.println(OffsetLimitInterceptor.class.getSimpleName()+".dialect="+dialectClass);
}
publicstaticclassBoundSqlSqlSourceimplementsSqlSource {
BoundSql boundSql;
publicBoundSqlSqlSource(BoundSql boundSql) {
this.boundSql = boundSql;
}
publicBoundSql getBoundSql(Object parameterObject) {
returnboundSql;
}
}
}
2.ibatis3配置文件内容:
Xml代码
3.MysqlDialect实现
Java代码
publicclassMySQLDialectextendsDialect{
publicbooleansupportsLimitOffset(){
returntrue;
}
publicbooleansupportsLimit() {
returntrue;
}
publicString getLimitString(String sql,intoffset,intlimit) {
returngetLimitString(sql,offset,String.valueOf(offset),limit,String.valueOf(limit));
}
publicString getLimitString(String sql,intoffset,String offsetPlaceholder,intlimit, String limitPlaceholder) {
if(offset >) {
returnsql +" limit "+offsetPlaceholder+","+limitPlaceholder;
} else{
returnsql +" limit "+limitPlaceholder;
}
}
}
其它数据库的Dialect实现请查看:
4.分页使用
直接调用SqlSession.selectList(statement, parameter, new RowBounds(offset,limit))即可使用物理分页
5.存在的问题
现不是使用占位符的方式(即不是“limit ?,?”而是使用“limit 8,20”)使用分页
完整代码可以查看即将发布的rapid-framework 3.0的ibatis3插件