最近有接觸到Mybatis的分頁處理,網上找了一些資料,實踐了一些實現方法,也寫一下博客,做爲學習的記錄。java
網上給出的通用的方式基本上都是:經過Mybatis的攔截,從新拼接SQL(添上limit,offset)。不少地方理解不到位,還只是停留在借鑑了直接使用的狀況。
mysql
一、首先定義一些基礎數據類Page,PageContext,Dialect,MySqlDialectsql
package com.sg.base.page; import java.util.List; public class Page implements java.io.Serializable { private static final long serialVersionUID = -8322296629302943918L; protected int pageSize = 10; // 每頁默認10條數據 protected int currentPage = 1; // 當前頁 protected int totalPages = 0; // 總頁數 protected int totalRows = 0; // 總數據數 protected int pageStartRow = 0; // 每頁的起始行數 protected int pageEndRow = 0; // 每頁顯示數據的終止行數 //get、set方法 public Page() {} public void init(int totalRows) { this.init(totalRows, this.pageSize, this.currentPage); } /** * 初始化分頁參數:須要先設置totalRows */ public void init(int rows, int pageSize,int currentPage ) { this.pageSize = pageSize; this.totalRows = rows; this.totalPages = (this.totalRows/this.pageSize)+(this.totalRows%this.pageSize>0?1:0); if(currentPage>0)gotoPage(currentPage); } /** * 計算當前頁的取值範圍:pageStartRow和pageEndRow */ private void calculatePage() { hasPreviousPage = (currentPage - 1) > 0?true:false; hasNextPage = currentPage >= totalPages?false:true; int pageStartRecord = currentPage * pageSize; boolean isLessThanTotalRows = pageStartRecord < totalRows; pageEndRow = isLessThanTotalRows?pageStartRecord:totalRows; pageStartRow = isLessThanTotalRows?pageEndRow - pageSize:pageSize * (totalPages - 1); } /** * 直接跳轉到指定頁數的頁面 */ public void gotoPage(int page) { currentPage = page; calculatePage(); debug(); } public void debug() { System.out.println("要顯示的頁面數據已經封裝,具體信息以下:"); String debug = "共有數據數:" + totalRows + "\n共有頁數:" + totalPages + "\n當前頁數爲:" + currentPage + "\n是否有前一頁:" + hasPreviousPage + "是否有下一頁:" + hasNextPage + "\n開始行數:" + pageStartRow + "\n終止行數:" + pageEndRow; System.out.println(debug); } boolean hasNextPage = false; // 是否有下一頁 boolean hasPreviousPage = false; // 是否有前一頁 public boolean isHasNextPage() { return hasNextPage; } public boolean isHasPreviousPage() { return hasPreviousPage; } //用的datatable分頁插件,有用到一下參數(高版本datatable的參數變了) protected int sEcho; //當前頁 protected int iTotalRecords; //總記錄數 protected int iTotalDisplayRecords; //顯示總記錄數 private List<?> aaData; // 返回的結果 //get、set方法 }
/** * 主要做用:在當前請求線程中保存分頁對象,使得分頁對象能夠在線程內共享,從而達到更小的依賴性 */ public class PageContext extends Page { private static final long serialVersionUID = 2491790900505242096L; private static ThreadLocal<PageContext> context = new ThreadLocal<PageContext>(); public static PageContext getContext(){ PageContext ci = context.get(); if(ci == null) { ci = new PageContext(); context.set(ci); } return ci; } public static void removeContext() { context.remove(); } protected void initialize() {} }
/** * 相似hibernate的Dialect,但只精簡出分頁部分 */ public class Dialect { public boolean supportsLimit(){ return false; } public boolean supportsLimitOffset() { return supportsLimit(); } /** * 將sql變成分頁sql語句,直接使用offset,limit的值做爲佔位符.</br> * 源代碼爲: getLimitString(sql,offset,String.valueOf(offset),limit,String.valueOf(limit)) */ public String getLimitString(String sql, int offset, int limit) { return getLimitString(sql,offset,Integer.toString(offset),limit,Integer.toString(limit)); } /** * 將sql變成分頁sql語句,提供將offset及limit使用佔位符(placeholder)替換. * <pre> * 如mysql * dialect.getLimitString("select * from user", 12, ":offset",0,":limit") 將返回 * select * from user limit :offset,:limit * </pre> * @return 包含佔位符的分頁sql */ public String getLimitString(String sql, int offset,String offsetPlaceholder, int limit,String limitPlaceholder) { throw new UnsupportedOperationException("paged queries not supported"); } }
public class MySqlDialect extends Dialect { public String getLimitString(String sql, int offset, int limit) { sql = sql.trim(); StringBuffer pagingSelect = new StringBuffer(sql.length() + 100); pagingSelect.append(sql); pagingSelect.append(" limit "+offset+" , "+(offset + limit)); return pagingSelect.toString(); } }
二、在Mybatis配置文件(mybatis-config.xml)中添加攔截數據庫
<plugins> <plugin interceptor="com.sg.base.page.PaginationInterceptor"> <property name="dialectClass" value="com.sg.base.page.MySqlDialect"/> </plugin> </plugins>
三、編寫攔截類PaginationInterceptorapache
package com.sg.base.page; //只攔截select部分 @Intercepts({@Signature(type=Executor.class,method="query",args={ MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class })}) public class PaginationInterceptor implements Interceptor{ private final static Log log = LogFactory.getLog(PaginationInterceptor.class); //數據庫方言類 private static String dialectClass; public Object intercept(Invocation invocation) throws Throwable { MappedStatement mappedStatement=(MappedStatement)invocation.getArgs()[0]; String id = mappedStatement.getId(); log.debug("MappedStatement id = "+id); //這裏用的方式是查詢方法帶PAGEQUERY關鍵字 if(id.toUpperCase().contains("PAGEQUERY")){ PageContext page=PageContext.getContext(); Object parameter = invocation.getArgs()[1]; BoundSql boundSql = mappedStatement.getBoundSql(parameter); if(boundSql==null || StringUtils.isEmpty(boundSql.getSql())) return null; String originalSql = boundSql.getSql().trim();//originalSql==null?boundSql.getSql().trim():originalSql; //獲得總記錄數 這裏看是否須要傳遞總記錄數以免重複得到總記錄數? int totalRecord = countRecords(originalSql,mappedStatement,boundSql); //分頁計算 page.init(totalRecord); final Dialect dialect; try { //加載實例化配置文件中的具體的Dialect Class<? extends Dialect> clazz = (Class<? extends Dialect>) Class.forName(dialectClass); dialect = clazz.newInstance(); } catch (Exception e) { throw new ClassNotFoundException("Cannot create dialect instance: " + dialectClass, e); } //添加上limit,offset String pagesql=dialect.getLimitString(originalSql, page.getPageStartRow(),page.getPageSize()); invocation.getArgs()[2] = RowBounds.DEFAULT; //複製獲得新的MappedStatement MappedStatement newMappedStatement = copyFromNewSql(mappedStatement, boundSql, pagesql, boundSql.getParameterMappings(), boundSql.getParameterObject()); invocation.getArgs()[0]= newMappedStatement; } return invocation.proceed(); } /** * 攔截器對應的封裝原始對象的方法 */ public Object plugin(Object target) { return Plugin.wrap(target, this); } /** * 設置註冊攔截器時設定的屬性 */ public void setProperties(Properties properties) { log.debug("================ setProperties ==========="); dialectClass = properties.getProperty("dialectClass"); if (dialectClass == null || "".equals(dialectClass)) { throw new RuntimeException("Mybatis分頁插件 ExecutorInterceptor 沒法獲取 dialectClass 參數!"); } } private int countRecords(String originalSql,MappedStatement mappedStatement,BoundSql boundSql) throws SQLException{ Connection connection = null; PreparedStatement countStmt = null; ResultSet rs = null; try{ int totalRecorld = 0; Object paramObject = boundSql.getParameterObject(); StringBuilder countSql = new StringBuilder(originalSql.length()+100 ); countSql.append("select count(1) from (").append(originalSql).append(") t"); connection = mappedStatement.getConfiguration().getEnvironment().getDataSource().getConnection(); countStmt = connection.prepareStatement(countSql.toString()); BoundSql countBS = copyFromBoundSql(mappedStatement, boundSql, countSql.toString(), boundSql.getParameterMappings(), paramObject); setParameters(countStmt,mappedStatement,countBS,paramObject); rs = countStmt.executeQuery(); if (rs.next()) { totalRecorld = rs.getInt(1); } return totalRecorld; }finally{ if(rs!=null)try{rs.close();}catch(Exception e){}; if(countStmt!=null)try{countStmt.close();}catch(Exception e){}; if(connection!=null)try{connection.close();}catch(Exception e){}; } } public static class BoundSqlSqlSource implements SqlSource { BoundSql boundSql; public BoundSqlSqlSource(BoundSql boundSql) { this.boundSql = boundSql; } public BoundSql getBoundSql(Object parameterObject) { return boundSql; } } /** * 對SQL參數(?)設值,參考org.apache.ibatis.executor.parameter.DefaultParameterHandler * @param ps * @param mappedStatement * @param boundSql * @param parameterObject * @throws java.sql.SQLException */ @SuppressWarnings({ "unchecked", "rawtypes" }) 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 = null; String propertyName = parameterMapping.getProperty(); PropertyTokenizer prop = new PropertyTokenizer(propertyName); if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX)&&boundSql.hasAdditionalParameter(prop.getName())) { value = boundSql.getAdditionalParameter(prop.getName()); if (value != null) { value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length())); } } else { value = metaObject == null ? null : metaObject.getValue(propertyName); } 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()); } } } } private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) { Builder builder = new Builder(ms.getConfiguration(),ms.getId(), newSqlSource, ms.getSqlCommandType()); builder.resource(ms.getResource()); builder.fetchSize(ms.getFetchSize()); builder.statementType(ms.getStatementType()); builder.keyGenerator(ms.getKeyGenerator()); String[] keys = ms.getKeyProperties(); if(keys!=null){ String keysstr = Arrays.toString(keys); keysstr = keysstr.replace("[",""); keysstr = keysstr.replace("]",""); builder.keyProperty(keysstr); } builder.timeout(ms.getTimeout()); builder.parameterMap(ms.getParameterMap()); builder.resultMaps(ms.getResultMaps()); builder.cache(ms.getCache()); MappedStatement newMs = builder.build(); return newMs; } private MappedStatement copyFromNewSql(MappedStatement mappedStatement, BoundSql boundSql, String sql, List<ParameterMapping> parameterMappings, Object parameter) { BoundSql newBoundSql = copyFromBoundSql(mappedStatement, boundSql, sql, parameterMappings, parameter); return copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql)); } private BoundSql copyFromBoundSql(MappedStatement mappedStatement, BoundSql boundSql, String sql, List<ParameterMapping> parameterMappings, Object parameter) { BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), sql, parameterMappings, parameter); for (ParameterMapping mapping : boundSql.getParameterMappings()){ String prop = mapping.getProperty(); if (boundSql.hasAdditionalParameter(prop)) { newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop)); } } return newBoundSql; } }