Mybatis分頁

最近有接觸到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;
    }
}
相關文章
相關標籤/搜索