(注:以下代码是基于ibatis3 beta4的扩展,ibatis3正式版如果实现改变,将会继续跟进修改)
iBatis3默认使用的分页是基于游标的分页,而这种分页在不同的数据库上性能差异不一致,最好的办法当然是使用类似hibernate的基于方言(Dialect)的物理分页功能。
iBatis3现在提供插件功能,通过插件我们可以编写自己的拦截器来拦截iBatis3的主要执行方法来完成相关功能的扩展。
能够拦截的的类如下:
Executor (update,query,flushStatements,commit,rollback,getTransaction,close,isClosed) ParameterHandler (getParameterObject,setParameters) ResultSetHandler (handleResultSets,handleOutputParameters) StatementHandler (prepare,parameterize,batch,update,query)
但此次我们感兴趣的是Executor.query()方法,查询方法最终都是执行该方法。
该方法完整是:
Executor.query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
分页方言的基本实现:
以Mysql数据库示例,即有一个方法 query(sql,offset,limit);
query("select * from user",5,10);
经过我们的拦截器拦截处理,变成
query("select * from user limit 5,10",0,0);
1.拦截类实现:
@Intercepts({@Signature( type= Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class OffsetLimitInterceptor implements Interceptor{ static int MAPPED_STATEMENT_INDEX = 0; static int PARAMETER_INDEX = 1; static int ROWBOUNDS_INDEX = 2; static int RESULT_HANDLER_INDEX = 3; Dialect dialect; public Object intercept(Invocation invocation) throws Throwable { processIntercept(invocation.getArgs()); return invocation.proceed(); } void processIntercept(final Object[] queryArgs) { //queryArgs = query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) MappedStatement ms = (MappedStatement)queryArgs[MAPPED_STATEMENT_INDEX]; Object parameter = queryArgs[PARAMETER_INDEX]; final RowBounds rowBounds = (RowBounds)queryArgs[ROWBOUNDS_INDEX]; int offset = rowBounds.getOffset(); int limit = 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, 0, limit); } limit = RowBounds.NO_ROW_LIMIT; queryArgs[ROWBOUNDS_INDEX] = new RowBounds(offset,limit); BoundSql newBoundSql = new BoundSql(sql, boundSql.getParameterMappings(), boundSql.getParameterObject()); MappedStatement newMs = copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql)); queryArgs[MAPPED_STATEMENT_INDEX] = newMs; } } private MappedStatement copyFromMappedStatement(MappedStatement ms,SqlSource newSqlSource) { Builder builder = new MappedStatement.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(); return newMs; } public Object plugin(Object target) { return Plugin.wrap(target, this); } public void setProperties(Properties properties) { String dialectClass = new PropertiesHelper(properties).getRequiredString("dialectClass"); try { dialect = (Dialect)Class.forName(dialectClass).newInstance(); } catch (Exception e) { throw new RuntimeException("cannot create dialect instance by dialectClass:"+dialectClass,e); } System.out.println(OffsetLimitInterceptor.class.getSimpleName()+".dialect="+dialectClass); } public static class BoundSqlSqlSource implements SqlSource { BoundSql boundSql; public BoundSqlSqlSource(BoundSql boundSql) { this.boundSql = boundSql; } public BoundSql getBoundSql(Object parameterObject) { return boundSql; } } }
2.ibatis3配置文件内容:
<configuration> <plugins> <!-- 指定数据库分页方言Dialect, 其它方言:OracleDialect,SQLServerDialect,SybaseDialect,DB2Dialect,PostgreSQLDialect,MySQLDialect,DerbyDialect--> <plugin interceptor="cn.org.rapid_framework.ibatis3.plugin.OffsetLimitInterceptor"> <property name="dialectClass" value="cn.org.rapid_framework.jdbc.dialect.MySQLDialect"/> </plugin> </plugins> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test" /> <property name="username" value="root" /> <property name="password" value="123456" /> </dataSource> </environment> </environments> <mappers> <mapper resource="com/company/project/model/mapper/BlogMapper.xml" /> </mappers> </configuration>
3.MysqlDialect实现
public class MySQLDialect extends Dialect{ public boolean supportsLimitOffset(){ return true; } public boolean supportsLimit() { return true; } public String getLimitString(String sql, int offset, int limit) { return getLimitString(sql,offset,String.valueOf(offset),limit,String.valueOf(limit)); } public String getLimitString(String sql, int offset,String offsetPlaceholder, int limit, String limitPlaceholder) { if (offset > 0) { return sql + " limit "+offsetPlaceholder+","+limitPlaceholder; } else { return sql + " limit "+limitPlaceholder; } } }
其它数据库的Dialect实现请查看:
4.分页使用
直接调用SqlSession.selectList(statement, parameter, new RowBounds(offset,limit))即可使用物理分页
5.存在的问题
现不是使用占位符的方式(即不是“limit ?,?”而是使用“limit 8,20”)使用分页
完整代码可以查看即将发布的rapid-framework 3.0的ibatis3插件