`
yvfish
  • 浏览: 263104 次
  • 性别: Icon_minigender_1
  • 来自: 合肥
社区版块
存档分类
最新评论

基于JDBC的通用分页实现

阅读更多
分页是一个被讲到烂掉的话题,今天我再拾起来踹几脚吧
(Hibernate的分页做得很好很强大,用的人都知道 ,这个就不用再说了)
1.为什么要分页?
   首先是数据量太大会影响查询和传输的性能,关键是对用户来说一下看到数万条记录也不是那么友好。
2.有哪些分页技术?
a)存储过程分页
   在数据库中创建一个存储过程,传入SQL和页码获得当前页的记录。需要对具体数据库的语法相当熟悉才能够编写,当然也可以直接google。性能最好,但不跨数据库平台。
b)数据库专有sql特性分页
   使用数据库专有的特性(MSSQL的top、Oracle的rownum、MySQL的limit等)实现当前页记录提取。性能也非常好,但也不跨数据库平台。
(为什么非要跨数据库平台呢?好吧,如果你的项目确定是不会换数据库的那就这么写吧。)
c)纯JDBC分页
   通过Statement的setMaxRow(endIndex)和rs.absoulte(beginIndex)仅取得当前页范围内的记录。此种方式的性能依赖于厂商对JDBC规范的实现,如果厂商的rs读取是以流的形式进行的,性能还是有所保障的(好吧我承认是这不负责的说法)。这种方式的通用性是最好的,完全与数据库平台无关了(牺牲性能换取跨平台特性是Java平台的常用手法,嘿嘿嘿)。
d)根据数据库类型自动生成数据库专有特性的sql语句  
   其实这就是Hibernate的实现方法,如果你觉得自己分析SQL语法然后将SQL转换为特定数据库语法比较麻烦,那就用Hibernate吧。如果你不想用Hibernate还是想用这种方法,那就麻烦你多辛苦一点,用力写出来给大家分享吧。

我们来理一理分页的逻辑吧
首先,对用户而言他是不关心你是怎么分页的,他只要给你一个页码,然后你把那一页的记录给他就行了。
为了能够取得指定页码所对应的记录,我们还需要两个关键的参数:每页记录数、总记录数。
   每页记录数可以设个默认值,20、30都行吧。
   总记录数就需要额外从数据库中取得了,这个也是没办法的事,谁让ResultSet没提供获取记录总数的方法呢。
通过这两个参数能够计算出来总页数,同时也就可以判断用户给出的页码是否有效了(超出最后一页或者小于第一页),然后根据页码和每页记录数就可以算出来当前页的记录起止范围了。(这些个算术都是小学的,就不用说了吧,其实我也是想了半天的)
为了表示方便,就把这些参数写成一个类,同时把计算方法也写进去

/**
 * 
 * @author Lixor(at)live.cn
 *
 */
public class Page {
	private int rowTotal;// 总记录数
	private int pageSize = 10;// 每页记录数

	private int count;// 当前页码
	
	private int total;// 总页数
	private int beginIndex;//起始记录下标
	private int endIndex;//截止记录下标

	/**
	 * 使用总记录数、当前页码构造
	 * 
	 * @param rowTotal
	 * @param count
	 *            页码,从1开始
	 */
	public Page(int totalRow, int count) {
		this.rowTotal = totalRow;
		this.count = count;
		calculate();
	}

	/**
	 * 使用总记录数、当前页码和每页记录数构造
	 * 
	 * @param rowTotal
	 * @param count
	 *            页码,从1开始
	 * @param pageSize
	 *            默认30条
	 */
	public Page(int totalRow, int count, int pageSize) {
		this.rowTotal = totalRow;
		this.count = count;
		this.pageSize = pageSize;
		calculate();
	}

	private void calculate() {
		total = rowTotal / pageSize + ((rowTotal % pageSize) > 0 ? 1 : 0);

		if (count > total) {
			count = total;
		} else if (count < 1) {
			count = 1;
		}

		beginIndex = (count - 1) * pageSize ;
		endIndex = beginIndex + pageSize ;
		if (endIndex > rowTotal) {
			endIndex = rowTotal;
		}
	}

	public int getCount() {
		return count;
	}

	public int getTotal() {
		return total;
	}

	public int getTotalRow() {
		return rowTotal;
	}

	public int getPageSize() {
		return pageSize;
	}

	public int getBeginIndex() {
		return beginIndex;
	}

	public int getEndIndex() {
		return endIndex;
	}

}


好了,可别散扯了,直接来个纯JDBC分页的示例吧

//从页面取得页码
int pageCount = 1;
try {
	pageCount = Integer.parseInt(request.getParameter("page.count"));
} catch (Exception ex) {}

//取得总记录数,创建Page对象
int totalRow = productDao.getProductAmount();//通过select count 取得总记录数
Page page = new Page(totalRow, pageCount);
productDao.list(page);



ProductDao.java
public List<Product> list(Page page) {
	List<Product> productList = new ArrayList<Product>();

	try {
		String sql = "SELECT p.id,p.name,p.price,p.productDate,p.image,p.type_id,t.name,p.description FROM tbl_product p,tbl_type t WHERE p.type_id=t.id";
		Connection conn = null;
		try {
			conn = DbUtil.getConnection();
			Statement st = conn.createStatement();
			st.setMaxRows(page.getEndIndex());//关键代码,设置最大记录数为当前页记录的截止下标
			ResultSet rs = st.executeQuery(sql);
			if (page.getBeginIndex() > 0) {
				rs.absolute(page.getBeginIndex());//关键代码,直接移动游标为当前页起始记录处
			}
			while (rs.next()) {
				Product product = new Product();
				……
				productList.add(product);
			}
			rs.close();
			st.close();
		} finally {
			if (conn != null) {
				conn.close();
			}
		}
	} catch (SQLException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	return productList;
}


最后我试了一下在MySQL上产品表里有10W数据时,执行的日志大概是
第1页 [http-8080-2] DEBUG org.logicalcobwebs.proxool.db_pms  - SELECT p.id,p.name,p.price,p.productDate,p.image,p.type_id,t.name,p.description FROM tbl_product p,tbl_type t WHERE p.type_id=t.id;  (0 milliseconds)

第3页 [http-8080-2] DEBUG org.logicalcobwebs.proxool.db_pms  - SELECT p.id,p.name,p.price,p.productDate,p.image,p.type_id,t.name,p.description FROM tbl_product p,tbl_type t WHERE p.type_id=t.id;  (0 milliseconds)

第5页 [http-8080-2] DEBUG org.logicalcobwebs.proxool.db_pms  - SELECT p.id,p.name,p.price,p.productDate,p.image,p.type_id,t.name,p.description FROM tbl_product p,tbl_type t WHERE p.type_id=t.id;  (0 milliseconds)

第10页 [http-8080-1] DEBUG org.logicalcobwebs.proxool.db_pms  - SELECT p.id,p.name,p.price,p.productDate,p.image,p.type_id,t.name,p.description FROM tbl_product p,tbl_type t WHERE p.type_id=t.id;  (3 milliseconds)

第30页 [http-8080-1] DEBUG org.logicalcobwebs.proxool.db_pms  - SELECT p.id,p.name,p.price,p.productDate,p.image,p.type_id,t.name,p.description FROM tbl_product p,tbl_type t WHERE p.type_id=t.id;  (6 milliseconds)

第100页 [http-8080-2] DEBUG org.logicalcobwebs.proxool.db_pms  - SELECT p.id,p.name,p.price,p.productDate,p.image,p.type_id,t.name,p.description FROM tbl_product p,tbl_type t WHERE p.type_id=t.id;  (15 milliseconds)

第1000页 [http-8080-2] DEBUG org.logicalcobwebs.proxool.db_pms  - SELECT p.id,p.name,p.price,p.productDate,p.image,p.type_id,t.name,p.description FROM tbl_product p,tbl_type t WHERE p.type_id=t.id;  (152 milliseconds)

第10000页 [http-8080-2] DEBUG org.logicalcobwebs.proxool.db_pms  - SELECT p.id,p.name,p.price,p.productDate,p.image,p.type_id,t.name,p.description FROM tbl_product p,tbl_type t WHERE p.type_id=t.id;  (2030 milliseconds)


很显然纯JDBC分页的性能与页码有关(需要查询的数据范围,也就是说st.setMaxRow是有效的),越往后翻,性能越差。
不过话说回来没有哪个用户去无聊的自己一页一页翻到第10000页去,一般都是翻两页找不到那就会进一步缩小查询条件的范围了。
所以折衷的办法就是一方面页面记录数搞大一点,另外就是限制用户翻页的范围,超过10页就直接提示用户缩小查询范围,如果这样的话纯JDBC分页也就可以凑和着用了。

[2015.11.25更新]
ORACLE JDBC对于翻页支持非常不友好,因为它会在rs.absoulte之前就会把setMaxRow后的查询结果集全部载入应用服务器内存,在数据量较大时会导致内存溢出。
3
0
分享到:
评论
1 楼 paladin1988 2012-11-23  
踹的好,我也来踹几脚。。

相关推荐

    统一实现分页接口

    #### 二、不同数据库下的分页实现 ##### Oracle 分页实现 在Oracle数据库中,可以利用ROWNUM伪列来实现分页。ROWNUM为每一行分配一个唯一的行号,基于此特性,我们可以构造如下的分页查询语句: ```sql SELECT * ...

    struts2- 2.3.15.3 spring3.2.4 mybatis-3.2.3 通用分页(不同数据库) 拦截器

    这个压缩包文件的标题和描述暗示了它们在实现通用分页功能时的应用,特别是涉及到在不同的数据库环境下工作。下面我们将深入探讨这三个框架以及分页和拦截器的概念。 Struts2是一个基于MVC(Model-View-Controller...

    各种实现分页技术

    以下是一些常见的Java分页实现方式: 1. **基于数据库的分页**:大多数数据库系统都支持分页查询,例如SQL的`LIMIT`和`OFFSET`(MySQL)或`ROWNUM`(Oracle)。在Java中,你可以通过PreparedStatement设置这两个...

    目前最好的JSP分页技术

    #### JSP中的分页实现 在JSP中实现分页,通常涉及到以下几个关键步骤: 1. **查询总记录数**:首先需要获取满足条件的记录总数,以便计算总页数和当前页显示的数据范围。 2. **计算页码和数据偏移量**:根据用户...

    java实现es sql分页,采用SSM架构实现,开箱即用

    本文将探讨如何在SSM架构下,利用Java实现基于SQL的Elasticsearch分页查询,从而实现“开箱即用”的功能。 首先,我们需要理解Elasticsearch的基本概念。Elasticsearch是一个分布式、RESTful风格的搜索和分析引擎,...

    基于MVC模式的数据库分页显示

    综上所述,基于MVC模式的数据库分页显示,不仅解决了大量数据的高效显示问题,还通过分离关注点提高了代码的可维护性和复用性。通过数据库连接池和数据封装,进一步优化了性能和资源管理。这种设计方法在实际的电子...

    仿照百度分页样式的代码

    本代码示例实现了一个类似百度网站分页显示数据的功能。适用于网站开发中需要对大量数据进行分页展示的场景,例如新闻列表、论坛帖子等。 #### 二、重要概念与术语解释 1. **分页技术**:在Web开发中,当需要展示的...

    ssh+baseDao实现分页

    本话题主要围绕如何利用SSH框架结合BaseDao实现分页功能来展开。 首先,让我们了解一下SSH框架的核心组件: 1. **Spring**:这是一个全面的开源应用框架,提供了依赖注入(DI)和面向切面编程(AOP)等功能,使得...

    java实现分页技术

    首先,我们定义一个`Pageable`接口,它扩展了`java.sql.ResultSet`接口,目的是为了在不破坏原有JDBC接口的基础上添加分页功能。`Pageable`接口包含了获取总页数、当前页记录数、分页大小、跳转指定页、设置分页大小...

    基于ssm,集成分页插件,多数据源切换,通用mapper插件,日志等基本框架

    这个项目标题提到的是一个基于SSM框架的基础工程,已经集成了分页插件、多数据源切换、通用Mapper插件和日志管理等功能,旨在提高开发效率和代码的可维护性。 1. **Spring框架**:Spring作为核心容器,负责管理应用...

    jsp 分页示例源码

    **四、JSP分页实现步骤** 1. **建立数据库连接**:使用Java的JDBC(Java Database Connectivity)API建立与SQL Server 2005的连接。 2. **编写SQL查询**:构建带有OFFSET和FETCH子句的SQL查询,根据请求参数(当前...

    GBase JDBC.zip

    GBase JDBC驱动还支持事务管理、批处理、预编译的SQL语句、结果集的滚动和分页等功能,使得Java开发者可以充分利用GBase数据库的特性和优势,进行复杂的数据操作和处理。在实际应用中,开发者需要根据项目需求和性能...

    java Pagination(分页并操作)

    综上所述,Java中的分页实现多种多样,可以根据项目需求和所使用的框架选择合适的方法。无论哪种方式,关键在于理解分页的核心原理,即通过偏移量和限制大小来控制数据的获取范围。合理地运用分页技术,既能优化...

    ssh三大框架通用配置

    在SSH框架中实现分页查询,通常在Service层计算总记录数,然后在DAO层使用Hibernate的`setFirstResult()`和`setMaxResults()`方法进行分页。Struts配置中,Action需要接收并处理分页参数(如当前页和每页条数),并...

    springboot-mybatis-分页

    PageHelper是MyBatis的优秀分页插件,它支持MyBatis和MyBatis-Spring,可以非常方便地实现分页查询。PageHelper通过拦截器的方式,自动拦截SQL,实现物理分页。使用PageHelper后,只需要在Mapper接口的方法上添加@...

    JSP分页技术文档下载

    然而,随着JDBC2.0的推出,Resultset增加了前后滚动的功能,为实现分页提供了可能。 分页通常有两种实现方式: 1. 数据库特定的方法:某些数据库,如MySQL和Oracle,提供了内置的分页功能。例如,MySQL可以通过`...

    基于SpringBoot+FreeMarker+MyBatis实现的一个通用后台管理系统

    本项目是一个基于SpringBoot、FreeMarker和MyBatis框架实现的通用后台管理系统,适用于本科毕业设计,具有实际操作价值并经过测试验证,确保能够正常运行。以下将详细讲解这三个技术栈以及它们在后台管理系统的应用...

    论文研究-可重用Java数据库操作组件的设计和实现.pdf

    基于Data Access Object(DAO)设计模式,利用抽象、接口、类反射技术设计和实现了可重用Java数据库操作组件。它封装了注册和载入JDBC驱动程序、建立数据库连接、运行Structured Query Language(SQL)语句及处理查询...

    反射泛型完美版分页.rar

    在实际应用中,这样的分页实现可以广泛用于Web应用程序,尤其是在基于Spring框架的项目中,可以方便地与DAO层和Service层集成,提供强大的数据检索和展示功能。 总的来说,"反射泛型完美版分页.rar"提供的解决方案...

    pager-taglib 分页扩展实例

    pager-taglib 是个很好的jsp分页标签,使用它结合jstl可以实现灵活的分页导航功能。在实际的开发中post方式的提交比较常见,本人做了一个比较通用的基于post方式的一个应用。主要实现一下功能: 1.添加输入跳转、每...

Global site tag (gtag.js) - Google Analytics