您的位置:首页 > 编程语言 > Java开发

Java数据分页

2015-09-25 23:47 429 查看




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代码

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层代码实现该接口。



Java代码

public abstract class BaseDao<T> implements IBaseDao<T>, IPagination<T>



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



下面是DAO基类代码。

Java代码

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中即可获得相应的数据。

Java代码

@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";

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: