【Java面试题】之分页功能的实现

以下内容是根据网上内容以及传智播客教学整理而来,侵删。


百度知道网友@starstroll 的回答:

 
 
分页的实现可分为两大类:一、数据在Java代码中进行分页,然后取得当前页数据;二、在数据库中直接取得当前页数据。


通常面试官都希望听到后者,因为那才是高效的方法。你如果想让面试官觉得你的能力高的话你就先否定他的问题,你可以回答说:“Java中根本不需要做分页的实现代码只管拿数据库中的当前页数据即可,数据分页功能应该交由SQL处理,在分页实现中Java最多只实现总页数的计算,除此以外几乎不用管。”如果你这么答的话面试官通常会问你总页数的算法,至于这个你可以网上找个高效点的方法,我现在知道最高效的就是:(数据总行数+每页数据行数-1)/每页数据行数。


算法可能有更高效的,你可以到网上找找。记住只在面试中才能这么答,笔试的话老老实实写出实现方法。否定面试官的问题会让他觉得你更professional,但不要太嚣张不然适得其反的。


本文中的分页基于Mysql实现,使用limit关键字。Oracle中没有limit,使用的是三层嵌套。关键是理解分页的实现,原理都是一样的

不使用任何框架,采用JSP+servlet实现

先介绍一下Mysql中Limit关键字

LIMIT 子句可以被用于强制 SELECT 语句返回指定的记录数。LIMIT 接受一个或两个数字参数。参数必须是一个整数常量。

如果给定两个参数,第一个参数指定第一个返回记录行的偏移量,第二个参数指定返回记录行的最大数目。

初始记录行的偏移量是 0(而不是 1):

  mysql> SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15
  为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:
  mysql> SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last.
  如果只给定一个参数,它表示返回最大的记录行数目:
  mysql> SELECT * FROM table LIMIT 5; //检索前 5 个记录行
  换句话说,LIMIT n 等价于 LIMIT 0,n。

总结:查询语句应该为(最关键的优两个参数,每页记录数和当前页)

SELECT * FROM 表 LIMIT (当前页-1)*每页显示记录数,每页显示的记录数


原理分析图如下:(核心是分页的JavaBean设计)




实现流程

0.      环境准备

a)        引入jar文件及引入配置文件

                                      i.             数据库驱动包

                                    ii.             C3P0连接池jar文件 及 配置文件

                                   iii.             DbUtis组件:    QueryRunner qr = new QueryRuner(dataSouce);

qr.update(sql);

b)        公用类: JdbcUtils.java

1.      先设计:PageBean.java

2.      Dao接口设计/实现:   2个方法

3.      Service/servlet

4.      JSP


代码实现:

1.C3P0配置文件 c3p0-config.xml(放在src目录下)


<c3p0-config>
	<default-config>
		<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc_demo
		</property>
		<property name="driverClass">com.mysql.jdbc.Driver</property>
		<property name="user">root</property>
		<property name="password">root</property>
		<property name="initialPoolSize">3</property>
		<property name="maxPoolSize">6</property>
		<property name="maxIdleTime">1000</property>
	</default-config>


	<named-config name="oracle_config">
		<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc_demo</property>
		<property name="driverClass">com.mysql.jdbc.Driver</property>
		<property name="user">root</property>
		<property name="password">root</property>
		<property name="initialPoolSize">3</property>
		<property name="maxPoolSize">6</property>
		<property name="maxIdleTime">1000</property>
	</named-config>


</c3p0-config>


2.JdbcUtil工具类

package cn.itcast.utils;

import javax.sql.DataSource;

import org.apache.commons.dbutils.QueryRunner;

import com.mchange.v2.c3p0.ComboPooledDataSource;

/**
 * 工具类
 * 1. 初始化C3P0连接池
 * 2. 创建DbUtils核心工具类对象
 * @author Jie.Yuan
 *
 */
public class JdbcUtils {

	/**
	 *  1. 初始化C3P0连接池
	 */
	private static  DataSource dataSource;
	static {
		dataSource = new ComboPooledDataSource();
	}
	
	/**
	 * 2. 创建DbUtils核心工具类对象
	 */
	public static QueryRunner getQueryRuner(){
		// 创建QueryRunner对象,传入连接池对象
		// 在创建QueryRunner对象的时候,如果传入了数据源对象;
		// 那么在使用QueryRunner对象方法的时候,就不需要传入连接对象;
		// 会自动从数据源中获取连接(不用关闭连接)
		return new QueryRunner(dataSource);
	}
}

3.核心类:PageBean.java

 

package cn.itcast.utils;

import java.util.List;

import cn.itcast.entity.Employee;

/**
 * 封装分页的参数
 * 
 * @author Jie.Yuan
 * 
 */
public class PageBean<T> {
	private int currentPage = 1; // 当前页, 默认显示第一页
	private int pageCount = 4;   // 每页显示的行数(查询返回的行数), 默认每页显示4行
	private int totalCount;      // 总记录数
	private int totalPage;       // 总页数 = 总记录数 / 每页显示的行数  (+ 1)
	private List<T> pageData;       // 分页查询到的数据
	
	// 返回总页数
	public int getTotalPage() {
		if (totalCount % pageCount == 0) {
			totalPage = totalCount / pageCount;
		} else {
			totalPage = totalCount / pageCount + 1;
		}
		return totalPage;
	}
		
//......省略getter和setter方法
}

  4.实体类设计 Employee.java(与数据库中的字段一一对应)

package cn.itcast.entity;

/**
 * 1. 实体类设计 (因为用了DbUtils组件,属性要与数据库中字段一致)
 * @author Jie.Yuan
 *
 */
public class Employee {

	private int empId;			// 员工id
	private String empName;		// 员工名称
	private int dept_id;		// 部门id
	
	
        //......省略getter和setter方法


5.接下来是DAO数据访问层的设计,先设计接口,然后再设计实现类(IEmployeeDao.java &EmployeeDaoImpl.java)

package cn.itcast.dao;

import cn.itcast.entity.Employee;
import cn.itcast.utils.PageBean;

/**
 * 2. 数据访问层,接口设计
 * @author Jie.Yuan
 *
 */
public interface IEmployeeDao {

	/**
	 * 分页查询数据
	 */
	public void getAll(PageBean<Employee> pb);
	
	/**
	 * 查询总记录数
	 */
	public int getTotalCount();
}

实现类

package cn.itcast.dao.impl;

import java.sql.SQLException;
import java.util.List;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import cn.itcast.dao.IEmployeeDao;
import cn.itcast.entity.Employee;
import cn.itcast.utils.JdbcUtils;
import cn.itcast.utils.PageBean;

/**
 * 2. 数据访问层实现
 * @author Jie.Yuan
 *
 */
public class EmployeeDao implements IEmployeeDao {

	@Override
	public void getAll(PageBean<Employee> pb) {
		
		//2. 查询总记录数;  设置到pb对象中
		int totalCount = this.getTotalCount();
		pb.setTotalCount(totalCount);
		
		/*
		 * 问题: jsp页面,如果当前页为首页,再点击上一页报错!
		 *              如果当前页为末页,再点下一页显示有问题!
		 * 解决:
		 * 	   1. 如果当前页 <= 0;       当前页设置当前页为1;
		 * 	   2. 如果当前页 > 最大页数;  当前页设置为最大页数
		 */
		// 判断
		if (pb.getCurrentPage() <=0) {
			pb.setCurrentPage(1);					    // 把当前页设置为1
		} else if (pb.getCurrentPage() > pb.getTotalPage()){
			pb.setCurrentPage(pb.getTotalPage());		// 把当前页设置为最大页数
		}
		
		//1. 获取当前页: 计算查询的起始行、返回的行数
		int currentPage = pb.getCurrentPage();
		int index = (currentPage -1 ) * pb.getPageCount();		// 查询的起始行
		int count = pb.getPageCount();							// 查询返回的行数
		
		
		//3. 分页查询数据;  把查询到的数据设置到pb对象中
		String sql = "select * from employee limit ?,?";
		
		try {
			// 得到Queryrunner对象
			QueryRunner qr = JdbcUtils.getQueryRuner();
			// 根据当前页,查询当前页数据(一页数据)
			List<Employee> pageData = qr.query(sql, new BeanListHandler<Employee>(Employee.class), index, count);
			// 设置到pb对象中
			pb.setPageData(pageData);
			
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		
	}

	@Override
	public int getTotalCount() {
		String sql = "select count(*) from employee";
		try {
			// 创建QueryRunner对象
			QueryRunner qr = JdbcUtils.getQueryRuner();
			// 执行查询, 返回结果的第一行的第一列
			Long count = qr.query(sql, new ScalarHandler<Long>());
			return count.intValue();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

}


7.service层设计,同样是先接口,后实现类

IEmployeeService.java & EmployeeService.java

package cn.itcast.service;

import cn.itcast.entity.Employee;
import cn.itcast.utils.PageBean;

/**
 * 3. 业务逻辑层接口设计
 * @author Jie.Yuan
 *
 */
public interface IEmployeeService {

	/**
	 * 分页查询数据
	 */
	public void getAll(PageBean<Employee> pb);
}


实现类

package cn.itcast.service.impl;

import cn.itcast.dao.IEmployeeDao;
import cn.itcast.dao.impl.EmployeeDao;
import cn.itcast.entity.Employee;
import cn.itcast.service.IEmployeeService;
import cn.itcast.utils.PageBean;

/**
 * 3. 业务逻辑层,实现
 * @author Jie.Yuan
 *
 */
public class EmployeeService implements IEmployeeService {
	
	// 创建Dao实例
	private IEmployeeDao employeeDao = new EmployeeDao();

	@Override
	public void getAll(PageBean<Employee> pb) {
		try {
			employeeDao.getAll(pb);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

}


8.最终,表现层Servlet的设计IndexServlet.java

package cn.itcast.servlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.itcast.entity.Employee;
import cn.itcast.service.IEmployeeService;
import cn.itcast.service.impl.EmployeeService;
import cn.itcast.utils.PageBean;

/**
 * 4. 控制层开发
 * @author Jie.Yuan
 *
 */
public class IndexServlet extends HttpServlet {
	// 创建Service实例
	private IEmployeeService employeeService = new EmployeeService();
	// 跳转资源
	private String uri;

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		
		try {
			//1. 获取“当前页”参数;  (第一次访问当前页为null) 
			String currPage = request.getParameter("currentPage");
			// 判断
			if (currPage == null || "".equals(currPage.trim())){
				currPage = "1";  	// 第一次访问,设置当前页为1;
			}
			// 转换
			int currentPage = Integer.parseInt(currPage);
			
			//2. 创建PageBean对象,设置当前页参数; 传入service方法参数
			PageBean<Employee> pageBean = new PageBean<Employee>();
			pageBean.setCurrentPage(currentPage);
			
			//3. 调用service  
			employeeService.getAll(pageBean);    // 【pageBean已经被dao填充了数据】
			
			//4. 保存pageBean对象,到request域中
			request.setAttribute("pageBean", pageBean);
			
			//5. 跳转 
			uri = "/WEB-INF/list.jsp";
		} catch (Exception e) {
			e.printStackTrace();  // 测试使用
			// 出现错误,跳转到错误页面;给用户友好提示
			uri = "/error/error.jsp";
		}
		request.getRequestDispatcher(uri).forward(request, response);
		
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		this.doGet(request, response);
	}

}


9.这是真的最后一步了,设计jsp页面


<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!-- 引入jstl核心标签库 -->
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    
    <title>分页数据查询</title>
	<meta http-equiv="pragma" content="no-cache">
	<meta http-equiv="cache-control" content="no-cache">
	<meta http-equiv="expires" content="0">    
  </head>
  
  <body>
  	<table border="1" width="80%" align="center" cellpadding="5" cellspacing="0">
  		<tr>
  			<td>序号</td>
  			<td>员工编号</td>
  			<td>员工姓名</td>
  		</tr>
  		<!-- 迭代数据 -->
  		<c:choose>
  			<c:when test="${not empty requestScope.pageBean.pageData}">
  				<c:forEach var="emp" items="${requestScope.pageBean.pageData}" varStatus="vs">
  					<tr>
  						<td>${vs.count }</td>
  						<td>${emp.empId }</td>
  						<td>${emp.empName }</td>
  					</tr>
  				</c:forEach>
  			</c:when>
  			<c:otherwise>
  				<tr>
  					<td colspan="3">对不起,没有您查找的数据</td>
  				</tr>
  			</c:otherwise>
  		</c:choose>
  		
  		<tr>
  			<td colspan="3" align="center">
  				当前${requestScope.pageBean.currentPage }/${requestScope.pageBean.totalPage }页     
  				
  				<a href="${pageContext.request.contextPath }/index?currentPage=1">首页〉</a>
  				<a href="${pageContext.request.contextPath }/index?currentPage=${requestScope.pageBean.currentPage-1}">上一页 </a>
  				<a href="${pageContext.request.contextPath }/index?currentPage=${requestScope.pageBean.currentPage+1}">下一页 </a>
  				<a href="${pageContext.request.contextPath }/index?currentPage=${requestScope.pageBean.totalPage}">末页</a>
  			</td>
  		</tr>
  		
  	</table>
  </body>
</html>





下面的内容不知道怎么回事无法删除,无视就好



    
    





















  • 3
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值