Java分页原理与实践

by Kay 2017.7.24

一直都是知道分页,但是却没有动手写过,虽然实现起来不难,但是动手去实践一下还是有好处的。

分页原理

先说下自己理解的分页,所谓分页,也就是将数据结果以分段的展示,可以是分表,有上一页和下一页之分,还有就是类似于朋友圈动态那种,往下拉将会加载更多的动态,有的是点击加载更多,有的则是下拉滚动条,在我的理解中,这些都是分页。

一般实现分页有几种方式:
1. 使用Java自带的 List接口的 subList(int fromIndex,int endIndex)方法
使用这种方式的特点是一次要从数据库中取出所有的数据,然后进行分割,数据量少效率比较高,但是在很多条记录的情况下,一次全部取出性能也不是很好.
2. 使用数据库的SQL语句进行分页。
- 代表的有MySQL的 limit 关键字

select * from user limit 0,10 
 - PostgreSQL分页 
 select * from user limit 10 offset 0 
 - Oracle分页 
 select * from( 
 select s.* ,rownum rn 
 from (select * from user) s 
 where rownum<= 10 
 ) where rn>= 1

使用SQL语句进行分页特点是数据库的兼容性比较差,每种数据库的实现方式不同,但是针对一个种数据库来说,这种方式还是很适合的
3. 使用hibernate分页
使用Query或Criteria对象进行设置firstResult,maxResult即可,数据库兼容性强

使用分页

首先建立一个Pager类来对应分页的一些属性:

package kay.com.dto;

import java.util.List;

/**
 * Created by kay on 2017/7/23.
 * 分页类对象,用来封装分页的一些属性
 */
public class Pager<T> {

    private int pageSize;   //每页大小

    private int currentPage; //当前页码

    private int totalRecord;  //总共记录条数

    private int totalPage;  //一共多少页

    private List<T> dataList;  //显示的数据内容

    public Pager(int pageSize, int currentPage, int totalRecord, int totalPage, List<T> dataList) {
        this.pageSize = pageSize;
        this.currentPage = currentPage;
        this.totalRecord = totalRecord;
        this.totalPage = totalPage;
        this.dataList = dataList;       
    }
    //getter   setter
}

为了说的清楚点,来结合具体业务实践一下,假如有一个银行账户Account,要查看它的流水记录(BankWater),我们将流水记录进行分页,于是有一个查询数据库的方法,我们将它这么定义:

/**
     * 查询分页流水数据
     * @param accountId 账户的ID
     * @param pageNum  第几页
     * @param pageSize  每页大小
     * @return
     */
    Pager<BankWater> querySubBankWaters(String accountId,int pageNum,int pageSize);

接着我们来实现一下,为了看的清楚一点,这里用最原始的JDBC来实现,里面写了SQL:

/**
     * 分页查询
     * @param accountId
     * @param pageNum  第几页
     * @param pageSize  每页大小
     * @return
     */
    @Override
    public Pager<BankWater> querySubBankWaters(String accountId, int pageNum, int pageSize) {
        Connection connection=null;
        PreparedStatement psmt=null;
        ResultSet rs=null;
        Pager<BankWater> pager=null;
        List<BankWater> bankWaters=new ArrayList<>();

        StringBuilder sql=new StringBuilder("SELECT * from bankwater WHERE account_id=?");
        // 起始索引
        int fromIndex   = pageSize * (pageNum -1);
        sql.append(" limit "+fromIndex+", "+pageSize);  //使用mysql的limit关键字分页

        //查总记录数
        StringBuilder countSql = new StringBuilder("SELECT count(check_id) from bankwater WHERE account_id=? ");
        try {
            connection=DBUtils.getConnection();
            psmt=connection.prepareStatement(countSql.toString());
            psmt.setString(1, accountId);
            rs=psmt.executeQuery();
            int totalRecord=-1;
            while (rs.next()){
                //总记录数
                totalRecord=rs.getInt(1);
            }

            psmt=connection.prepareStatement(sql.toString());
            psmt.setString(1, accountId);
            rs= psmt.executeQuery();
            while(rs.next()){
                BankWater bankWater=new BankWater();
                bankWater.setCheckId(rs.getInt(1)+"");
                bankWater.setAccountId(rs.getString(2));
                bankWater.setCheckCount(rs.getBigDecimal(3));
                bankWater.setCheckTime(rs.getDate(4));
                bankWater.setCheckType(rs.getString(5));
                bankWater.setBalance(rs.getBigDecimal(6));
                bankWater.setCheckPlace(rs.getString(7));
                bankWaters.add(bankWater);
            }

            //获取总页数
            int totalPage = totalRecord / pageSize;
            if(totalRecord % pageSize !=0){
                totalPage++;
            }
            //创建Pager对象
     pager=new Pager<BankWater> pageSize,pageNum,totalRecord,totalPage,bankWaters);

        } catch (Exception e) {
            e.printStackTrace();

        }finally{
            DBUtils.release(connection, psmt, rs);
        }
        return pager;
    }

以上,我们获取所有Page的属性信息将其创建了出来然后返回,到此这个分页对象就已经可以使用了,该有的属性全都有了。

前端使用分页

在页码跳转和数据刷新的时候,我们需要每次都调用查询分页结果的方法。用Ajax请求来做可以不用刷新整个页面,这里我们将上面的数据库查询封装起来,在Service里面调用,然后在Servlet的GET方法里面调用Service,就像这样:

/**
     *根据账户查询该账户的流水记录
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String pageNumStr=request.getParameter("pageNum");
        int pageNum=1;
        if(pageNumStr!=null && !"".equals(pageNumStr.trim())){
            pageNum = Integer.parseInt(pageNumStr);
        }
        int pageSize=5;   //每页默认大小

        Account account= (Account) request.getSession().getAttribute("accountInfo");
        String accountId=account.getAccountId();

        //实例AccountService 查询流水
        AccountService accountService=new AccountServiceImpl();
        //查询所有记录
        //List<BankWater> list=accountService.queryBankWaters(accountId);

        //查询分页记录
        Pager<BankWater> bankWaters=accountService.queryBankWaters(accountId,pageNum,pageSize);

        //todo 注册Date转换器+BigDecimal,转换json格式需要

        JsonConfig jsonConfig = new JsonConfig();
        jsonConfig.registerJsonValueProcessor(java.util.Date.class, new JsonDateValueProcessor());
        jsonConfig.registerJsonValueProcessor(java.math.BigDecimal.class, new JsonBigDecimalProcessor());
        JSONArray jsonArray=new JSONArray();

        jsonArray=JSONArray.fromObject(bankWaters,jsonConfig);
        //System.out.println(jsonArray.toString());

        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(jsonArray.toString());


    }

在accountId跟具体业务关联时,我们只要传入一个pageNum即可,设置一个默认的每页显示数量(pageSize)
前端我们用jquery.pagination.js来帮助我们展示和使用,使用这个插件需要有jquery.js、jquery.pagination.js和pagination.css的支持:
JS代码如下:

// 点击分页按钮以后触发的动作,动态添加表单
    function handlePaginationClick(new_page_index, pagination_container) {
        //查询流水
        $.ajax({
            type: "get",
            url: "${pageContext.request.contextPath}/bankWaters?pageNum="+(new_page_index+1),
            dataType:"json",
            success: function(result) {
                var data=result[0].dataList;
                $("#result_body").empty();
                for(var i=0;i<data.length;i++){
                    $("#result_body").append("<tr>\n   " +
                        " <td>"+data[i].checkTime+"</td>\n    " +
                        "<td>"+data[i].checkId+"</td>\n    " +
                        "<td>"+data[i].checkType+"</td>\n    " +
                        "<td>"+data[i].checkCount+"</td>\n    " +
                        "<td>"+data[i].balance+"</td>\n    " +
                        "<td>"+data[i].checkPlace+"</td>\n" +
                        "</tr>>\n   ");
                }
            },
            fail:function(result){
                alert(result);
            }
        });
    }

    function inintpage(totalRecord,currentPage){
        $("#BankWaters-Pagination").pagination(totalRecord, {
            items_per_page:5, // 每页显示多少条记录
            current_page:currentPage-1, // 当前显示第几页数据
            num_display_entries:8, // 分页显示的条目数
            next_text:"下一页",
            prev_text:"上一页",
            num_edge_entries:2, // 连接分页主体,显示的条目数
            callback:handlePaginationClick
        });

    }

handlePaginationClick是一个回调方法,在每次点击页码的时候执行,也就是去后台查询分页数据。
于是我们还要一个第一次执行时的函数来请求加载时就出现的分页数据,请求和上面相同,
在这个方法里我们要调用inintpage方法来初始化分页插件:

$("#bankwaterbtn").click(function() {
        //点击查看流水,刷新表单
        $.ajax({
            type: "get",
            url: "${pageContext.request.contextPath}/bankWaters",
            dataType:"json",
            success: function(result) {
                var data=result[0].dataList;
                $("#result_body").empty();
                for(var i=0;i<data.length;i++){
                    $("#result_body").append("<tr>\n   " +
                        " <td>"+data[i].checkTime+"</td>\n    " +
                        "<td>"+data[i].checkId+"</td>\n    " +
                        "<td>"+data[i].checkType+"</td>\n    " +
                        "<td>"+data[i].checkCount+"</td>\n    " +
                        "<td>"+data[i].balance+"</td>\n    " +
                        "<td>"+data[i].checkPlace+"</td>\n" +
                        "</tr>>\n   ");
                }
                var totalRecord=result[0].totalRecord;
                var currentPage=result[0].currentPage;
                inintpage(totalRecord,currentPage);
            },
            fail:function(result){
                alert(result);
            }
        });

    });

来看一下最终的展示效果吧:

java中分页类newpager javaee分页功能的实现和原理_java中分页类newpager

很少写这种类似教程的总结,发现这样写比单纯的笔记式复制粘贴要思路清晰很多,希望多多支持,以后会更注意表达方式。Kay 2017.7.24