Mybatis 數據庫物理分頁插件 PageHelper 轉

以前使用ibatis/mybatis,都是自己手寫sql語句進行物理分頁,雖然稍微有點麻煩,但是都習慣了。最近試用了下mybatis的分頁插件 PageHelper,感覺還不錯吧。記錄下其使用方法。

1. 引入依賴jar包:

<dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper</artifactId>
        <version>3.7.5</version>
    </dependency>

2. 配置分頁攔截器

PageHelper的原理是基於攔截器實現的。攔截器的配置有兩種方法,一種是在mybatis的配置文件中配置,一種是直接在spring的配置文件中進行:

1)在mybatis-config.xml文件中配置:

複製代碼

<plugins>
    <!-- com.github.pagehelper爲PageHelper類所在包名 -->
    <plugin interceptor="com.github.pagehelper.PageHelper">
        <property name="dialect" value="mysql"/>
        <!-- 該參數默認爲false -->
        <!-- 設置爲true時,會將RowBounds第一個參數offset當成pageNum頁碼使用 -->
        <!-- 和startPage中的pageNum效果一樣-->
        <property name="offsetAsPageNum" value="true"/>
        <!-- 該參數默認爲false -->
        <!-- 設置爲true時,使用RowBounds分頁會進行count查詢 -->
        <property name="rowBoundsWithCount" value="true"/>
        
        <!-- 設置爲true時,如果pageSize=0或者RowBounds.limit = 0就會查詢出全部的結果 -->
        <!-- (相當於沒有執行分頁查詢,但是返回結果仍然是Page類型)
        <property name="pageSizeZero" value="true"/>-->
        
        <!-- 3.3.0版本可用 - 分頁參數合理化,默認false禁用 -->
        <!-- 啓用合理化時,如果pageNum<1會查詢第一頁,如果pageNum>pages會查詢最後一頁 -->
        <!-- 禁用合理化時,如果pageNum<1或pageNum>pages會返回空數據 -->
        <property name="reasonable" value="true"/>
        <!-- 3.5.0版本可用 - 爲了支持startPage(Object params)方法 -->
        <!-- 增加了一個`params`參數來配置參數映射,用於從Map或ServletRequest中取值 -->
        <!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默認值 -->
        <!-- 不理解該含義的前提下,不要隨便複製該配置 
        <property name="params" value="pageNum=start;pageSize=limit;"/>    -->
    </plugin>
  </plugins>

複製代碼

這裏要注意 <plugins> 在mybatis-config.xml文件中的位置,必須要符合 http://mybatis.org/dtd/mybatis-3-config.dtd 中指定的順序:

<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, 
    objectFactory?, objectWrapperFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>

不然會報錯。

當然mybatis-config.xml的位置,我們需要在spring的配置文件中進行指定:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
      <property name="dataSource" ref="dataSource" />
      <property name="configLocation" value="classpath:config/mybatis-config.xml" />
      <property name="mapperLocations" value="classpath*:config/mappers/**/*.xml" />
    </bean>

2)如果mybatis沒有mybatis-config.xml文件,那麼就只能直接在spring的配置文件中配置了:

複製代碼

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource"/>
  <property name="mapperLocations">
    <array>
      <value>classpath:config/mapper/*.xml</value>
    </array>
  </property>
  <property name="typeAliasesPackage" value="com.test.pojo"/>
  <property name="plugins">
    <array>
      <bean class="com.github.pagehelper.PageHelper">
        <property name="properties">
          <value>
            dialect=mysql
          </value>
        </property>
      </bean>
    </array>
  </property>
</bean>

複製代碼

到這裏PageHelper所需要的配置已經完成,下面還需要在serviceImpl類中加入分頁參數的代碼:

3. 向攔截器傳遞分頁參數

我們首先看下不分頁的serviceImpl代碼:

複製代碼

@Override
    public List<User> getUserByNoAndEmail(String no, String email) {
        Map<String, Object> map = new HashMap<>();
        map.put("no", no);
        map.put("email", email);
        return this.userMapper.getUserByNoAndEmail(map);
    }

複製代碼

然後我們將它改造成使用PageHelper分頁:

1)首先我們根據自己項目的情況,定義一個PageBean,來保存分頁之後的結果,需要哪些屬性,就加入哪些屬性,具體可以參考源代碼中的PageInfo類的定義,其實PageInfo是插件作者給我們自己定義自己的PageBean,提供的一個參考例子。PageInfo代碼如下:

複製代碼

@SuppressWarnings({"rawtypes", "unchecked"})
public class PageInfo<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    //當前頁
    private int pageNum;
    //每頁的數量
    private int pageSize;
    //當前頁的數量
    private int size;
    //由於startRow和endRow不常用,這裏說個具體的用法
    //可以在頁面中"顯示startRow到endRow 共size條數據"

    //當前頁面第一個元素在數據庫中的行號
    private int startRow;
    //當前頁面最後一個元素在數據庫中的行號
    private int endRow;
    //總記錄數
    private long total;
    //總頁數
    private int pages;
    //結果集
    private List<T> list;

    //第一頁
    private int firstPage;
    //前一頁
    private int prePage;
    //下一頁
    private int nextPage;
    //最後一頁
    private int lastPage;

    //是否爲第一頁
    private boolean isFirstPage = false;
    //是否爲最後一頁
    private boolean isLastPage = false;
    //是否有前一頁
    private boolean hasPreviousPage = false;
    //是否有下一頁
    private boolean hasNextPage = false;
    //導航頁碼數
    private int navigatePages;
    //所有導航頁號
    private int[] navigatepageNums;

    /**
     * 包裝Page對象
     *
     * @param list
     */
    public PageInfo(List<T> list) {
        this(list, 8);
    }

    /**
     * 包裝Page對象
     *
     * @param list          page結果
     * @param navigatePages 頁碼數量
     */
    public PageInfo(List<T> list, int navigatePages) {
        if (list instanceof Page) {
            Page page = (Page) list;
            this.pageNum = page.getPageNum();
            this.pageSize = page.getPageSize();

            this.total = page.getTotal();
            this.pages = page.getPages();
            this.list = page;
            this.size = page.size();
            //由於結果是>startRow的,所以實際的需要+1
            if (this.size == 0) {
                this.startRow = 0;
                this.endRow = 0;
            } else {
                this.startRow = page.getStartRow() + 1;
                //計算實際的endRow(最後一頁的時候特殊)
                this.endRow = this.startRow - 1 + this.size;
            }
            this.navigatePages = navigatePages;
            //計算導航頁
            calcNavigatepageNums();
            //計算前後頁,第一頁,最後一頁
            calcPage();
            //判斷頁面邊界
            judgePageBoudary();
        }
    }

    /**
     * 計算導航頁
     */
    private void calcNavigatepageNums() {
        //當總頁數小於或等於導航頁碼數時
        if (pages <= navigatePages) {
            navigatepageNums = new int[pages];
            for (int i = 0; i < pages; i++) {
                navigatepageNums[i] = i + 1;
            }
        } else { //當總頁數大於導航頁碼數時
            navigatepageNums = new int[navigatePages];
            int startNum = pageNum - navigatePages / 2;
            int endNum = pageNum + navigatePages / 2;

            if (startNum < 1) {
                startNum = 1;
                //(最前navigatePages頁
                for (int i = 0; i < navigatePages; i++) {
                    navigatepageNums[i] = startNum++;
                }
            } else if (endNum > pages) {
                endNum = pages;
                //最後navigatePages頁
                for (int i = navigatePages - 1; i >= 0; i--) {
                    navigatepageNums[i] = endNum--;
                }
            } else {
                //所有中間頁
                for (int i = 0; i < navigatePages; i++) {
                    navigatepageNums[i] = startNum++;
                }
            }
        }
    }

    /**
     * 計算前後頁,第一頁,最後一頁
     */
    private void calcPage() {
        if (navigatepageNums != null && navigatepageNums.length > 0) {
            firstPage = navigatepageNums[0];
            lastPage = navigatepageNums[navigatepageNums.length - 1];
            if (pageNum > 1) {
                prePage = pageNum - 1;
            }
            if (pageNum < pages) {
                nextPage = pageNum + 1;
            }
        }
    }

    /**
     * 判定頁面邊界
     */
    private void judgePageBoudary() {
        isFirstPage = pageNum == 1;
        isLastPage = pageNum == pages;
        hasPreviousPage = pageNum > 1;
        hasNextPage = pageNum < pages;
    }

    public void setPageNum(int pageNum) {
        this.pageNum = pageNum;
    }

    public int getPageNum() {
        return pageNum;
    }

    public int getPageSize() {
        return pageSize;
    }

    public int getSize() {
        return size;
    }

    public int getStartRow() {
        return startRow;
    }

    public int getEndRow() {
        return endRow;
    }

    public long getTotal() {
        return total;
    }

    public int getPages() {
        return pages;
    }

    public List<T> getList() {
        return list;
    }

    public int getFirstPage() {
        return firstPage;
    }

    public int getPrePage() {
        return prePage;
    }

    public int getNextPage() {
        return nextPage;
    }

    public int getLastPage() {
        return lastPage;
    }

    public boolean isIsFirstPage() {
        return isFirstPage;
    }

    public boolean isIsLastPage() {
        return isLastPage;
    }

    public boolean isHasPreviousPage() {
        return hasPreviousPage;
    }

    public boolean isHasNextPage() {
        return hasNextPage;
    }

    public int getNavigatePages() {
        return navigatePages;
    }

    public int[] getNavigatepageNums() {
        return navigatepageNums;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("PageInfo{");
        sb.append("pageNum=").append(pageNum);
        sb.append(", pageSize=").append(pageSize);
        sb.append(", size=").append(size);
        sb.append(", startRow=").append(startRow);
        sb.append(", endRow=").append(endRow);
        sb.append(", total=").append(total);
        sb.append(", pages=").append(pages);
        sb.append(", list=").append(list);
        sb.append(", firstPage=").append(firstPage);
        sb.append(", prePage=").append(prePage);
        sb.append(", nextPage=").append(nextPage);
        sb.append(", lastPage=").append(lastPage);
        sb.append(", isFirstPage=").append(isFirstPage);
        sb.append(", isLastPage=").append(isLastPage);
        sb.append(", hasPreviousPage=").append(hasPreviousPage);
        sb.append(", hasNextPage=").append(hasNextPage);
        sb.append(", navigatePages=").append(navigatePages);
        sb.append(", navigatepageNums=");
        if (navigatepageNums == null) sb.append("null");
        else {
            sb.append('[');
            for (int i = 0; i < navigatepageNums.length; ++i)
                sb.append(i == 0 ? "" : ", ").append(navigatepageNums[i]);
            sb.append(']');
        }
        sb.append('}');
        return sb.toString();
    }
}

複製代碼

因爲PageInfo.java只是一個示例,所以他定義得有點重量級,屬性有點多,我們可以參考它,定義適合我們自己的PageBean, 比如如下定義:

複製代碼

public class PageBean<T> implements Serializable {
    private static final long serialVersionUID = 8656597559014685635L;
    private long total;        //總記錄數
    private List<T> list;    //結果集
    private int pageNum;    // 第幾頁
    private int pageSize;    // 每頁記錄數
    private int pages;        // 總頁數
    private int size;        // 當前頁的數量 <= pageSize,該屬性來自ArrayList的size屬性
    
    /**
     * 包裝Page對象,因爲直接返回Page對象,在JSON處理以及其他情況下會被當成List來處理,
     * 而出現一些問題。
     * @param list          page結果
     * @param navigatePages 頁碼數量
     */
    public PageBean(List<T> list) {
        if (list instanceof Page) {
            Page<T> page = (Page<T>) list;
            this.pageNum = page.getPageNum();
            this.pageSize = page.getPageSize();
            this.total = page.getTotal();
            this.pages = page.getPages();
            this.list = page;
            this.size = page.size();
        }
    }

    public long getTotal() {
        return total;
    }

    public void setTotal(long total) {
        this.total = total;
    }

    public List<T> getList() {
        return list;
    }

    public void setList(List<T> list) {
        this.list = list;
    }

    public int getPageNum() {
        return pageNum;
    }

    public void setPageNum(int pageNum) {
        this.pageNum = pageNum;
    }

    public int getPageSize() {
        return pageSize;
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    public int getPages() {
        return pages;
    }

    public void setPages(int pages) {
        this.pages = pages;
    }

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }
    
}

複製代碼

因爲分頁查詢結果返回的是一個 Page 對象,而 Page 對象繼承自ArrayList,但是如果我們直接返回ArrayList的話,在一些場景下回遇到問題,比如在JSON處理Page類型的結果時,會被當成List來JSON格式化,會丟棄 Page 對象的所有擴展屬性,所以這裏我們要將分頁的結果 Page 類型轉換成我們自己定義的 PageBean. 我們自己定義的PageBean沒有繼承ArrayList,而是包含一個List屬性來保存分頁結果。所以避免前面的問題。

2)修改 serviceImpl中的代碼:

複製代碼

@Override
    public PageBean<User> getUserByNoAndEmail(String no, String email) {
        Map<String, Object> map = new HashMap<>();
        map.put("no", no);
        map.put("email", email);
        
        PageHelper.startPage(PaginationContext.getPageNum(), PaginationContext.getPageSize());
        List<User> list = this.userMapper.getUserByNoAndEmail(map);
        return new PageBean<User>(list);
    }

複製代碼

我們只需要使用 PageHelper.startPage(pageNum, pageSize); 函數來指定 pageNum(第幾頁) 和 pageSize(每頁顯示幾條記錄) 兩個參數。然後調用原來的查詢,就進行了分頁。最後將返回的List,轉換成 PageBean類型的結果即可。前臺頁面就可以根據PageBean中包括的屬性來進行分頁顯示了。

上面的 PaginationContext 是基於 ThreadLocal 來傳遞分頁參數的一個工具類,其實現如下:

複製代碼

public class PaginationContext {
    // 定義兩個threadLocal變量:pageNum和pageSize
    private static ThreadLocal<Integer> pageNum = new ThreadLocal<Integer>();    // 保存第幾頁
    private static ThreadLocal<Integer> pageSize = new ThreadLocal<Integer>();    // 保存每頁記錄條數

    /*
     * pageNum :get、set、remove
     */
    public static int getPageNum() {
        Integer pn = pageNum.get();
        if (pn == null) {
            return 0;
        }
        return pn;
    }

    public static void setPageNum(int pageNumValue) {
        pageNum.set(pageNumValue);
    }

    public static void removePageNum() {
        pageNum.remove();
    }

    /*
     * pageSize :get、set、remove
     */
    public static int getPageSize() {
        Integer ps = pageSize.get();
        if (ps == null) {
            return 0;
        }
        return ps;
    }

    public static void setPageSize(int pageSizeValue) {
        pageSize.set(pageSizeValue);
    }

    public static void removePageSize() {
        pageSize.remove();
    }
}

複製代碼

實現了前臺頁面向ServiceImpl中傳遞分頁參數: pageNum 和 pageSize.

當然從請求中獲取分頁參數pageNum和pageSize需要用到filter:

複製代碼

public class PageFilter implements Filter {
    public PageFilter() {}

    public void destroy() {}

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;

        PaginationContext.setPageNum(getPageNum(httpRequest));
        PaginationContext.setPageSize(getPageSize(httpRequest));

        try {
            chain.doFilter(request, response);
        }
        // 使用完Threadlocal,將其刪除。使用finally確保一定將其刪除
        finally {
            PaginationContext.removePageNum();
            PaginationContext.removePageSize();
        }
    }

    /**
     * 獲得pager.offset參數的值
     * 
     * @param request
     * @return
     */
    protected int getPageNum(HttpServletRequest request) {
        int pageNum = 1;
        try {
            String pageNums = request.getParameter("pageNum");
            if (pageNums != null && StringUtils.isNumeric(pageNums)) {
                pageNum = Integer.parseInt(pageNums);
            }
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
        return pageNum;
    }

    /**
     * 設置默認每頁大小
     * 
     * @return
     */
    protected int getPageSize(HttpServletRequest request) {
        int pageSize = 10;    // 默認每頁10條記錄
        try {
            String pageSizes = request.getParameter("pageSize");
            if (pageSizes != null && StringUtils.isNumeric(pageSizes)) {
                pageSize = Integer.parseInt(pageSizes);
            }
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
        return pageSize;
    }

    /**
     * @see Filter#init(FilterConfig)
     */
    public void init(FilterConfig fConfig) throws ServletException {}

}

複製代碼

PageFilter在web.xml中的配置:

複製代碼

<!-- pagination filter -->
    <filter>
          <filter-name>PageFilter</filter-name>
          <filter-class>com.ems.filter.PageFilter</filter-class>
      </filter>
      <filter-mapping>
          <filter-name>PageFilter</filter-name>
          <url-pattern>/*</url-pattern>
      </filter-mapping>

複製代碼

OK,到此,PageHelper的使用方法,基本結束。

PageHelper 項目地址:http://git.oschina.net/free/Mybatis_PageHelper

文檔地址:http://git.oschina.net/free/Mybatis_PageHelper/blob/master/wikis/HowToUse.markdown

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章