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,用来保存存分页信息和数据。
package com.emerson.etao.utils; import java.util.List; /** * 数据分页接口 * * @author Chris Mao(Zibing) * */ public interface IPagination<T> { /** * 每页显示的数据记录数 */ public static final int PAGE_SIZE = 20; /** * 设置当前页面索引值 * * @param pageIndex */ public void setCurrentPage(int pageIndex); /** * 设置总行数,并计算出分页数 * * @param totalRows */ public void setTotalRows(int totalRows); /** * 当前页面索索值 * * @return int */ public int getCurrentPage(); /** * 总行数 * * @return int */ public int getTotalRows(); /** * 总页面数 * * @return int */ public int getTotalPages(); /** * 当前页面的数据记录集合 * * @return List<T> */ public List<T> getData(); }
然后在DAO层代码实现该接口。
public abstract class BaseDao<T> implements IBaseDao<T>, IPagination<T>
但是实践下来发现,这样会破坏DAO层代码的原子性,所以打算使用单独的类来实现该接口。但是又考虑到这个类,不可能单独使用,它需要使用到DAO类中的代码与数据库交互,比如查询记录总行数,于是这里使用了Java内部类特性,在DAO代码内声明一个内部类并实现接口IPagination。
下面是DAO基类代码。
package com.emerson.etao.dao; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emerson.etao.db.DBUtils; import com.emerson.etao.utils.IPagination; /** * * 数据访问层基类 * * 所有数据访问对象都需要继承此类 * * @author Chris Mao(Zibing) * */ public abstract class BaseDao<T> implements IBaseDao<T> { protected static final Logger logger = LoggerFactory.getLogger(BaseDao.class); /** * 获取数据库连接 * * @return java.sql.Connection */ public Connection getConnection() { return DBUtils.getConnection(); } /** * 关闭数据库连接 * * @param conn * @see java.sql.Connection */ public void closeConnection(Connection conn) { DBUtils.closeConnection(conn); } /** * 关闭Statement对象,会将对应的数据库连接一并关闭 * * @param stmt * @see java.sql.Statement */ @Override public void closeStatement(Statement stmt) { DBUtils.closeStatement(stmt); } /** * 创建Statement对象,如果参数readOnly为true,则创建一个只读的Statement对象 * * @param conn * @param readOnly * @return * @see java.sql.Connection * @see java.sql.Statement */ @Override public Statement createStatement(Connection conn, boolean readOnly) { return DBUtils.createStatement(conn, readOnly); } @Override public int getTotalRowCount(String sqlStr) throws SQLException { int result = 0; Connection conn = this.getConnection(); Statement stmt = this.createStatement(conn, true); try { ResultSet rs = stmt.executeQuery(String.format("SELECT COUNT(*) AS F1 FROM (%s) AS T1", sqlStr)); while (rs.next()) { result = rs.getInt(1); } rs.close(); return result; } finally { this.closeStatement(stmt); } } /** * 分页查询对象 * * @author Chris Mao(Zibing) * */ protected class Pagination implements IPagination<T> { /** * 当前页号 */ private int currentPage; /** * 总记录数 */ private int totalRows; /** * 总页数 */ private int totalPages; /** * 查询偏移量 */ private int offset; /** * 查询数据的SQL语句 */ private String sqlStatement = null; public Pagination(String sqlStatement) { this.sqlStatement = sqlStatement; } @Override public int getCurrentPage() { return currentPage; } @Override public void setCurrentPage(int pageIndex) { if (pageIndex < 1) { currentPage = 1; } else if (pageIndex > totalPages) { currentPage = totalPages; } else { currentPage = pageIndex; } offset = (currentPage - 1) * IPagination.PAGE_SIZE; } @Override public int getTotalRows() { return this.totalRows; } @Override public void setTotalRows(int totalRows) { this.totalRows = totalRows; if (totalRows % IPagination.PAGE_SIZE == 0) { totalPages = totalRows / IPagination.PAGE_SIZE; } else { totalPages = totalRows / IPagination.PAGE_SIZE + 1; } } @Override public int getTotalPages() { return totalPages; } @Override public List<T> getData() { List<T> result = null; try { result = getAll(getMysqlPageSQL()); } catch (SQLException e) { e.printStackTrace(); } return result; } /** * * @return */ public String getMysqlPageSQL() { return sqlStatement.concat(String.format(" limit %d, %d ", offset, IPagination.PAGE_SIZE)); } } public IPagination<T> getPagination(int pageIndex, String sqlStr) throws SQLException { Pagination pagination = new Pagination(sqlStr); pagination.setTotalRows(getTotalRowCount(sqlStr)); pagination.setCurrentPage(pageIndex); return pagination; } }
内部类Pagination的构造函数需要传入一个字符串参数,这是因为需要将数据库查询语句传入到该类中,用于后期拼装成带有limit关键字的分页查询语句。
BaseDao类提供了一个公开方法getPagination(int pageIndex, String sqlStr)用于获取该接口实例。该方法的两个参数分别是需要查询的页码和用于查询数据的SQL语句。
有些细心的读者可能发现了,每次调用getPagination方法时,其中的pagination.setTotalRows(getTotalRowCount(sqlStr))语句也总会被执行,这个显得有些多余,因为第一次调用getPagination方法时,就已经计算过数据的总行数及总页数,没有必要每次调用都重新计算一次。
但是请大家停下来思考一下,我们设计的是Web程序,当某个用户在查看数据时,系统反馈回来的数据量是N,如果此时恰巧有另一个用户正在向服务器提交新的数据,这时数据量已变成N+1了。那么如果我们只在程序最初读取一次数据总行数、计算总页数,就无法正确的将最新的数据分页信息推送到客户端,导致第一个用户看到的数据与服务器上真实的数据不匹配。
经过上述设计,在Controller中处理客户的数据请求,就变得非常轻松简洁了。客户只要将请求的数据页码作为URL参数传入到Controller中即可获得相应的数据。
@RequestMapping(value = "/{pageIndex}", method = RequestMethod.GET) public String list(@PathVariable int pageIndex, Model model) { IPagination<Communicator> page = getCommunicatorService().pagingQuery(pageIndex); List<Communicator> list = page.getData(); model.addAttribute("pageIndex", page.getCurrentPage()); model.addAttribute("totalPages", page.getTotalPages()); model.addAttribute("totalRows", page.getTotalRows()); model.addAttribute("list", list); return "communicator/list"; }
Service层的代码也是很简洁的
public IPagination<T> pagingQuery(int pageIndex) { logger.info("PageIndex: " + pageIndex); try { return this.getDao().getPagination(pageIndex, "SELECT * FROM vw_communicator"); } catch (SQLException e) { e.printStackTrace(); } return null; }
相关推荐
java 数据分页显示 java 数据分页显示 java 数据分页显示
displaytag.rar java数据分页驱动 java 数据分页 驱动
实现基本的数据分页,特别适用于论坛等需要查询一些数据量比较大的网页的制作
分页缓存<用java实现分页,并读取过程先读取缓存数据>
Java海量数据分页Bean, 适用于Oracle(适当修改,适用于任何数据库).功能描述:传入到达页码(具有容错性)、每页记录数、Select查询语句,返回该页所有的记录(整页是List集合,每条记录是一个 HashMap)、总行数、总...
通过操作数据库实现数据分页功能,属于后台操作
java几种分页方法java几种分页方法java几种分页方法 java几种分页方法java几种分页方法java几种分页方法 java几种分页方法java几种分页方法java几种分页方法 java几种分页方法java几种分页方法java几种分页方法
java中分页功能的实现.直接调用即可.这个插件的原理就是你只要给他一个需要分页的集合,分页数,每页条数,然后在回调函数中返回总条数,当前页码,每页数据集合,每页条数,后台只需要调用即可。
java实现oracle分页策略完整版,可当做参考类使用,有详细注释,适合初学者。
java的动态树形菜单,和分页的实现,源码加数据库,可直接运行。
在java中用JSP进行数据分页显示的一个实现
用于数据量大的情况下预先查询出数据,加快对后面页面数据的查询速度
jsp和java联合实现了自定义标签的功能,主要技术继承了TagSupport类和编写对应bodytag.tld配置文件 实现了分页功能 当前功能点所在位置 循环遍历数据
源代码实现的实在Java web与Oracle数据开发过程中的分页功能,代码不难,看看就能看懂,使用其中的核心代码能够轻易实现分页功能。希望对初学者有一定的帮助。
java实现数据库数据分页显示,用到了伪列rownum,实现了高效分页。
//获取数据库中所查询的数据的list list = newgetresouce.getlistcount(); //初始化PaginationBean的构造方法 page.Init((ArrayList)list); //保存一个PaginationBean的对象,用于在页面中调用其中...
大数据 分页 算法
java jdbc 分页工具类,以及返回集合数据的封装, private int limit = 10;//每页的个数 /** * 当前页 */ private int page; // /** * 总行数 */ private int totalRows; // /** * 总页数 */ private ...
我的方案是用两个TAG完成分页显示数据和页码,一个TAG(displaytag)专门显示数据,一个TAG(PagesSearchTag.java)专门显示页码,可以满足所有的分页要求,在JSP文件中调用这两个TAG既可完成分页任务。页码格式类似...
java对HighGo DB数据分页的示例,java对HighGo DB数据分页的示例