注:源码可参考https://github.com/yanmushi/mybatis-pagable
【输入项】
pageNumber: 当前页码
pageSize: 加载的记录数
dataTarget:数据模型
【输出项】
totalRows: 总记录数
totalPages: 总页码
List<data>: 最终返回的结果记录
主要的数据流程,如下图所示。
import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.sql.Connection; import java.util.Properties; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Plugin; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.factory.DefaultObjectFactory; import org.apache.ibatis.reflection.factory.ObjectFactory; import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory; import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; @Intercepts({@Signature( type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(args = { Connection.class }, method = "prepare", type = StatementHandler.class)}) public class PaginationInterceptor implements Interceptor, Serializable { private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory(); private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory(); private static final long serialVersionUID = 8477066981231187373L; private ExecutorProvider executorProvider = new SimpleExecutorProvider(); public void setExecutorProvider(ExecutorProvider executorProvider) { this.executorProvider = executorProvider; } @Override public Object intercept(Invocation invocation) throws Throwable { if (invocation.getTarget() instanceof StatementHandler) { return handleStatementHandler(invocation); } return invocation.proceed(); } /** * @param invocation * @return * @throws IllegalAccessException * @throws InvocationTargetException */ private Object handleStatementHandler(Invocation invocation) throws InvocationTargetException, IllegalAccessException { StatementHandler statementHandler = (StatementHandler) invocation .getTarget(); MetaObject metaStatementHandler = MetaObject.forObject( statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY); RowBounds rowBounds = (RowBounds) metaStatementHandler .getValue("delegate.rowBounds"); if (rowBounds == null || (rowBounds.getOffset() == RowBounds.NO_ROW_OFFSET && rowBounds .getLimit() == RowBounds.NO_ROW_LIMIT)) { return invocation.proceed(); } // 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环可以分离出最原始的的目标类) while (metaStatementHandler.hasGetter("h")) { Object object = metaStatementHandler.getValue("h"); metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY); } // 分离最后一个代理对象的目标类 while (metaStatementHandler.hasGetter("target")) { Object object = metaStatementHandler.getValue("target"); metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY); } // 将mybatis的内存分页,调整为物理分页 BoundSql boundSql = (BoundSql) metaStatementHandler .getValue("delegate.boundSql"); String sql = boundSql.getSql(); // 重写sql String pageSql = sql + " LIMIT " + rowBounds.getOffset() + "," + rowBounds.getLimit(); metaStatementHandler.setValue("delegate.boundSql.sql", pageSql); // 将执行权交给下一个拦截器 return invocation.proceed(); } @Override public Object plugin(Object o) { if (executorProvider.isSupport(o)) { return Plugin.wrap(executorProvider.create((Executor) o), this); } else if (o instanceof StatementHandler) { return Plugin.wrap(o, this); } return o; } @Override public void setProperties(Properties properties) { } }
然后plugin方法中,写了一个监听了分页执行器的工场,接口如下:
public interface ExecutorProvider { /** * 是否支持当前拦截的对象信息 */ boolean isSupport(Object o); /** * 创建对应处理的执行器 */ Executor create(Executor o); }
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import org.apache.ibatis.cache.CacheKey; import org.apache.ibatis.executor.BatchResult; import org.apache.ibatis.executor.ErrorContext; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.executor.ExecutorException; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.mapping.ParameterMode; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.transaction.Transaction; import org.apache.ibatis.type.TypeHandler; import org.apache.ibatis.type.TypeHandlerRegistry; public class PaginationExecutor implements Executor { private Executor executor; private CountSqlProvider countSqlProvider; private String pattern; public PaginationExecutor(Executor executor) { this.executor = executor; } public PaginationExecutor(Executor executor, CountSqlProvider countSqlProvider) { this.executor = executor; this.countSqlProvider = countSqlProvider; } public PaginationExecutor(Executor o, CountSqlProvider countSqlProvider, String pattern) { this(o, countSqlProvider); this.pattern = pattern; } @Override public int update(MappedStatement ms, Object parameter) throws SQLException { return executor.update(ms, parameter); } @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException { List<E> rows = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql); return pageResolver(rows, ms, parameter, rowBounds); } private <E> List<E> pageResolver(List<E> rows, MappedStatement ms, Object parameter, RowBounds rowBounds) { String msid = ms.getId(); if (msid.matches(pattern)) { int count = getCount(ms, parameter); int offset = rowBounds.getOffset(); int pagesize = rowBounds.getLimit(); return new PageResult<E>(rows, new Pagination(count, pagesize, offset)); } return rows; } private int getCount(MappedStatement ms, Object parameter) { BoundSql bsql = ms.getBoundSql(parameter); String sql = bsql.getSql(); String countSql = countSqlProvider.getCountSQL(sql); Connection connection = null; PreparedStatement stmt = null; ResultSet rs = null; try { // get connection connection = ms.getConfiguration().getEnvironment().getDataSource() .getConnection(); stmt = connection.prepareStatement(countSql); setParameters(stmt, ms, bsql, parameter); rs = stmt.executeQuery(); if (rs.next()) return rs.getInt(1); } catch (SQLException e) { e.printStackTrace(); } finally { try { if (connection != null && !connection.isClosed()) { connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } return 0; } @SuppressWarnings("unchecked") private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws SQLException { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { Configuration configuration = mappedStatement.getConfiguration(); TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject); for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else { value = metaObject == null ? null : metaObject.getValue(propertyName); } @SuppressWarnings("rawtypes") TypeHandler typeHandler = parameterMapping.getTypeHandler(); if (typeHandler == null) { throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName + " of statement " + mappedStatement.getId()); } typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType()); } } } } @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { List<E> rows = executor.query(ms, parameter, rowBounds, resultHandler); return pageResolver(rows, ms, parameter, rowBounds); } @Override public List<BatchResult> flushStatements() throws SQLException { return executor.flushStatements(); } @Override public void commit(boolean required) throws SQLException { executor.commit(required); } @Override public void rollback(boolean required) throws SQLException { executor.rollback(required); } @Override public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { return executor .createCacheKey(ms, parameterObject, rowBounds, boundSql); } @Override public boolean isCached(MappedStatement ms, CacheKey key) { return executor.isCached(ms, key); } @Override public void clearLocalCache() { executor.clearLocalCache(); } @Override public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) { executor.deferLoad(ms, resultObject, property, key, targetType); } @Override public Transaction getTransaction() { return executor.getTransaction(); } @Override public void close(boolean forceRollback) { executor.close(forceRollback); } @Override public boolean isClosed() { return executor.isClosed(); } public CountSqlProvider getCountSqlProvider() { return countSqlProvider; } public void setCountSqlProvider(CountSqlProvider countSqlProvider) { this.countSqlProvider = countSqlProvider; } public Executor getExecutor() { return executor; } public void setExecutor(Executor executor) { this.executor = executor; } }
通过以上代码,基本完成了分页结果的一个查询及封装。
其中还有两个类是实现分页模型的,把接口分享一下。
public class PageData<T> implements Pageable<T> { protected Pagination pagination; protected List<T> datas; public PageData() { } /** * @param pagination * @param datas */ public PageData(Pagination pagination, List<T> datas) { super(); this.pagination = pagination; this.datas = datas; } @Override public Pagination getPagination() { return pagination; } @Override public List<T> getDatas() { return datas; } public void setPagination(Pagination pagination) { this.pagination = pagination; } public void setDatas(List<T> datas) { this.datas = datas; } }
public class Pagination { private long totalRows = -1; private int totalPages = 0; private int pageSize = 20; private int offset = 0; private int current = 1; private boolean init; public Pagination() { } public Pagination(int current, int pageSize) { this.current = current; this.pageSize = pageSize; this.offset = (current - 1) * pageSize; } public Pagination(long totalRows, int pageSize, int offset) { this.totalRows = totalRows; this.pageSize = pageSize; this.offset = offset; this.current = this.offset / this.pageSize + 1; countTotalPages(); } private void countTotalPages() { if (totalRows == -1) throw new IllegalArgumentException("uncountable pagination!"); this.totalPages = (int) this.totalRows / this.pageSize + (this.totalRows % this.pageSize == 0 ? 0 : 1); this.init = true; } public long getTotalRows() { if (!init) countTotalPages(); return totalRows; } public void setTotalRows(long totalRows) { this.totalRows = totalRows; } public int getTotalPages() { return totalPages; } public void setTotalPages(int totalPages) { this.totalPages = totalPages; } public int getPageSize() { return pageSize; } public void setPageSize(int pageSize) { this.pageSize = pageSize; } public int getOffset() { return offset; } public void setOffset(int offset) { this.offset = offset; } public int getCurrent() { return current; } public void setCurrent(int current) { this.current = current; } public boolean getHasNext() { if (!init) countTotalPages(); return current < totalPages; } public boolean getHasPrev() { if (!init) countTotalPages(); return current > 1; } @Override public String toString() { StringBuffer res = new StringBuffer(); res.append(getClass().getName()); res.append("["); res.append(current); res.append(","); res.append(pageSize); res.append(","); res.append(totalPages); res.append(","); res.append(totalRows); res.append("]"); return res.toString(); } }
PS.
关于MyBatis,基于RowBounds的分页实现。经过分析发现:
在org.apache.ibatis.executor.SimpleExecutor下,有如下代码:
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }
最终执行的查询结果,交给一个resultHandler来处理
protected void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException { if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) { if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) { rs.absolute(rowBounds.getOffset()); } } else { for (int i = 0; i < rowBounds.getOffset(); i++) rs.next(); } }
源码:https://github.com/yanmushi/mybatis-pagable