Java数据分页

来源:互联网 发布:php黑客技术网站源码 编辑:程序博客网 时间:2024/05/22 08:07


Java数据分页的设计及实现

 概述

数据分页,对于一个Web程序而言,是不可或缺的一个基础功能。当数据量很小很小的时候,比如只有只有二三十笔,不提供数据分页功能或许还是可以接受的;当数据量达到五十笔、八十笔的时候,如果还不提供分页功能,会显得有些差强人意了;当数据量达到上百、上千甚至上万笔的时候,如果再不提供分页功能,我想没有哪个用户是能够接受得了的了。

解决方案

数据分页,主要有两种解决方案:一是在数据库端进行分页查询;二是一次性将数据全部抓取到客户端,由客户端进行分页处理。这两种方案各有利弊,这里就不多赘述。通常使用第一种解决方案比较多,我这里也选择第一种方案,并以Mysql数据库为例,为大家讲解我的设计。

在数据库端进行分页查询,只需要使用Mysql数据库中自带的limit关键字即可实现,我需要的做的只是需要计算出数据偏移量,以及每次获取记录的笔数。

数据偏移量 = (页码 - 1) * 每页数据笔数

举例说明,假设我们每页显示20笔记录,第1页的偏移量就是(1-1)*20=0,即从第1笔记录开始,连续读取20笔记录;第2页偏移量就是(2-1)*20=20,即从第21笔记录开始,连续读取20笔记录...以此类推。

数据库的查询搞定了,下面就开始思考Java代码的设计。本文的代码设计是在上一篇博文《Java Spring MVC分层设计》的基础进行构建的。


代码交互时序图



 代码设计

最初的构想是这样的,定义一个接口IPagination,用来保存存分页信息和数据。

Java代码 
  1. package com.emerson.etao.utils;  
  2.   
  3. import java.util.List;  
  4.   
  5. /** 
  6.  * 数据分页接口 
  7.  *  
  8.  * @author Chris Mao(Zibing) 
  9.  * 
  10.  */  
  11. public interface IPagination<T> {  
  12.   
  13.     /** 
  14.      * 每页显示的数据记录数 
  15.      */  
  16.     public static final int PAGE_SIZE = 20;  
  17.   
  18.     /** 
  19.      * 设置当前页面索引值 
  20.      *  
  21.      * @param pageIndex 
  22.      */  
  23.     public void setCurrentPage(int pageIndex);  
  24.       
  25.     /** 
  26.      * 设置总行数,并计算出分页数 
  27.      *  
  28.      * @param totalRows 
  29.      */  
  30.     public void setTotalRows(int totalRows);  
  31.   
  32.     /** 
  33.      * 当前页面索索值 
  34.      *  
  35.      * @return int 
  36.      */  
  37.     public int getCurrentPage();  
  38.   
  39.     /** 
  40.      * 总行数 
  41.      *  
  42.      * @return int 
  43.      */  
  44.     public int getTotalRows();  
  45.   
  46.     /** 
  47.      * 总页面数 
  48.      *  
  49.      * @return int 
  50.      */  
  51.     public int getTotalPages();  
  52.   
  53.     /** 
  54.      * 当前页面的数据记录集合 
  55.      *  
  56.      * @return List<T> 
  57.      */  
  58.     public List<T> getData();  
  59.   
  60. }  

 

 然后在DAO层代码实现该接口。

 

Java代码 
  1. public abstract class BaseDao<T> implements IBaseDao<T>, IPagination<T>  

 

但是实践下来发现,这样会破坏DAO层代码的原子性,所以打算使用单独的类来实现该接口。但是又考虑到这个类,不可能单独使用,它需要使用到DAO类中的代码与数据库交互,比如查询记录总行数,于是这里使用了Java内部类特性,在DAO代码内声明一个内部类并实现接口IPagination。

 

下面是DAO基类代码。

Java代码 
  1. package com.emerson.etao.dao;  
  2.   
  3. import java.sql.Connection;  
  4. import java.sql.ResultSet;  
  5. import java.sql.SQLException;  
  6. import java.sql.Statement;  
  7. import java.util.List;  
  8.   
  9. import org.slf4j.Logger;  
  10. import org.slf4j.LoggerFactory;  
  11.   
  12. import com.emerson.etao.db.DBUtils;  
  13. import com.emerson.etao.utils.IPagination;  
  14.   
  15. /** 
  16.  *  
  17.  * 数据访问层基类 
  18.  *  
  19.  * 所有数据访问对象都需要继承此类 
  20.  *  
  21.  * @author Chris Mao(Zibing) 
  22.  * 
  23.  */  
  24. public abstract class BaseDao<T> implements IBaseDao<T> {  
  25.   
  26.     protected static final Logger logger = LoggerFactory.getLogger(BaseDao.class);  
  27.   
  28.     /** 
  29.      * 获取数据库连接 
  30.      *  
  31.      * @return java.sql.Connection 
  32.      */  
  33.     public Connection getConnection() {  
  34.         return DBUtils.getConnection();  
  35.     }  
  36.   
  37.     /** 
  38.      * 关闭数据库连接 
  39.      *  
  40.      * @param conn 
  41.      * @see java.sql.Connection 
  42.      */  
  43.     public void closeConnection(Connection conn) {  
  44.         DBUtils.closeConnection(conn);  
  45.     }  
  46.   
  47.     /** 
  48.      * 关闭Statement对象,会将对应的数据库连接一并关闭 
  49.      *  
  50.      * @param stmt 
  51.      * @see java.sql.Statement 
  52.      */  
  53.     @Override  
  54.     public void closeStatement(Statement stmt) {  
  55.         DBUtils.closeStatement(stmt);  
  56.     }  
  57.   
  58.     /** 
  59.      * 创建Statement对象,如果参数readOnly为true,则创建一个只读的Statement对象 
  60.      *  
  61.      * @param conn 
  62.      * @param readOnly 
  63.      * @return 
  64.      * @see java.sql.Connection 
  65.      * @see java.sql.Statement 
  66.      */  
  67.     @Override  
  68.     public Statement createStatement(Connection conn, boolean readOnly) {  
  69.         return DBUtils.createStatement(conn, readOnly);  
  70.     }  
  71.   
  72.     @Override  
  73.     public int getTotalRowCount(String sqlStr) throws SQLException {  
  74.         int result = 0;  
  75.         Connection conn = this.getConnection();  
  76.         Statement stmt = this.createStatement(conn, true);  
  77.         try {  
  78.             ResultSet rs = stmt.executeQuery(String.format("SELECT COUNT(*) AS F1 FROM (%s) AS T1", sqlStr));  
  79.             while (rs.next()) {  
  80.                 result = rs.getInt(1);  
  81.             }  
  82.             rs.close();  
  83.             return result;  
  84.         } finally {  
  85.             this.closeStatement(stmt);  
  86.         }  
  87.     }  
  88.   
  89.     /** 
  90.      * 分页查询对象 
  91.      *  
  92.      * @author Chris Mao(Zibing) 
  93.      * 
  94.      */  
  95.     protected class Pagination implements IPagination<T> {  
  96.         /** 
  97.          * 当前页号 
  98.          */  
  99.         private int currentPage;  
  100.   
  101.         /** 
  102.          * 总记录数 
  103.          */  
  104.         private int totalRows;  
  105.   
  106.         /** 
  107.          * 总页数 
  108.          */  
  109.         private int totalPages;  
  110.   
  111.         /** 
  112.          * 查询偏移量 
  113.          */  
  114.         private int offset;  
  115.   
  116.         /** 
  117.          * 查询数据的SQL语句 
  118.          */  
  119.         private String sqlStatement = null;  
  120.   
  121.         public Pagination(String sqlStatement) {  
  122.             this.sqlStatement = sqlStatement;  
  123.         }  
  124.   
  125.         @Override  
  126.         public int getCurrentPage() {  
  127.             return currentPage;  
  128.         }  
  129.   
  130.         @Override  
  131.         public void setCurrentPage(int pageIndex) {  
  132.             if (pageIndex < 1) {  
  133.                 currentPage = 1;  
  134.             } else if (pageIndex > totalPages) {  
  135.                 currentPage = totalPages;  
  136.             } else {  
  137.                 currentPage = pageIndex;  
  138.             }  
  139.             offset = (currentPage - 1) * IPagination.PAGE_SIZE;  
  140.         }  
  141.   
  142.         @Override  
  143.         public int getTotalRows() {  
  144.             return this.totalRows;  
  145.         }  
  146.   
  147.         @Override  
  148.         public void setTotalRows(int totalRows) {  
  149.             this.totalRows = totalRows;  
  150.             if (totalRows % IPagination.PAGE_SIZE == 0) {  
  151.                 totalPages = totalRows / IPagination.PAGE_SIZE;  
  152.             } else {  
  153.                 totalPages = totalRows / IPagination.PAGE_SIZE + 1;  
  154.             }  
  155.         }  
  156.   
  157.         @Override  
  158.         public int getTotalPages() {  
  159.             return totalPages;  
  160.         }  
  161.   
  162.         @Override  
  163.         public List<T> getData() {  
  164.             List<T> result = null;  
  165.             try {  
  166.                 result = getAll(getMysqlPageSQL());  
  167.             } catch (SQLException e) {  
  168.                 e.printStackTrace();  
  169.             }  
  170.             return result;  
  171.         }  
  172.   
  173.         /** 
  174.          *  
  175.          * @return 
  176.          */  
  177.         public String getMysqlPageSQL() {  
  178.             return sqlStatement.concat(String.format(" limit %d, %d ", offset, IPagination.PAGE_SIZE));  
  179.         }  
  180.     }  
  181.   
  182.     public IPagination<T> getPagination(int pageIndex, String sqlStr) throws SQLException {  
  183.         Pagination pagination = new Pagination(sqlStr);  
  184.         pagination.setTotalRows(getTotalRowCount(sqlStr));  
  185.         pagination.setCurrentPage(pageIndex);  
  186.         return pagination;  
  187.     }  
  188. }  

 

内部类Pagination的构造函数需要传入一个字符串参数,这是因为需要将数据库查询语句传入到该类中,用于后期拼装成带有limit关键字的分页查询语句。

BaseDao类提供了一个公开方法getPagination(int pageIndex, String sqlStr)用于获取该接口实例。该方法的两个参数分别是需要查询的页码和用于查询数据的SQL语句。

有些细心的读者可能发现了,每次调用getPagination方法时,其中的pagination.setTotalRows(getTotalRowCount(sqlStr))语句也总会被执行,这个显得有些多余,因为第一次调用getPagination方法时,就已经计算过数据的总行数及总页数,没有必要每次调用都重新计算一次。

但是请大家停下来思考一下,我们设计的是Web程序,当某个用户在查看数据时,系统反馈回来的数据量是N,如果此时恰巧有另一个用户正在向服务器提交新的数据,这时数据量已变成N+1了。那么如果我们只在程序最初读取一次数据总行数、计算总页数,就无法正确的将最新的数据分页信息推送到客户端,导致第一个用户看到的数据与服务器上真实的数据不匹配。

经过上述设计,在Controller中处理客户的数据请求,就变得非常轻松简洁了。客户只要将请求的数据页码作为URL参数传入到Controller中即可获得相应的数据。

Java代码 
  1. @RequestMapping(value = "/{pageIndex}", method = RequestMethod.GET)  
  2.     public String list(@PathVariable int pageIndex, Model model) {  
  3.         IPagination<Communicator> page = getCommunicatorService().pagingQuery(pageIndex);  
  4.         List<Communicator> list = page.getData();  
  5.         model.addAttribute("pageIndex", page.getCurrentPage());  
  6.         model.addAttribute("totalPages", page.getTotalPages());  
  7.         model.addAttribute("totalRows", page.getTotalRows());  
  8.         model.addAttribute("list", list);  
  9.         return "communicator/list";  
  10.     }  

 


0 0
原创粉丝点击