JavaWeb(十)JDBC——分页

本文介绍了使用JavaWeb和JDBC实现分页查询的方法。通过Servlet、DAO和服务层的配合,展示如何处理数据库查询并展示分页结果。内容包括分页的作用、组成、配置和实现步骤,同时涉及SQL的limit关键字以及动态生成分页页码列表的逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分页其实随处可见,例如说最常用的百度搜索后的结果显示,亦或者说论坛中的显示帖子列表。分页实现的方式有很多种,在这一章里主要讲的是用SQL语句结合Servlet来实现分页效果。

目录

分页

    作用

    组成

    例子

        配置

        实现

后话


分页

    作用

分页的作用其实很简单,就是将数据分割成多个页面来进行显示。若数据量过于庞大,在一个页面内全部显示,第一会影响到用户的体验,第二会加重服务器的承载负担。

    组成

每一个页面中需要包含哪一些才能实现分页的效果呢?在这里列出需要的数据:

    '''
        1.要显示的数据结果集:beanList,通过查询数据库得来。
        2.当前页:currentPage,通过页面传递到后台,默认第一页
        3.总页数:totalPage,通过后台计算得来
        4.总记录数:totalRecord,通过查询数据库得来
        5.每页记录数:pageSize,即每一页显示的记录条数
        6.请求参数:url
    '''

通过上面列举的发现,其实只有两个数据是从数据库中获取,其余的都是通过计算而来。那么需要计算的数据所用到的公式如下:

    总页数:
        totalPage = 总条数 % 每页记录数 == 0? 总条数/每页记录数 : 总条数/每页记录数+1

    例子

        配置

    这里以学生信息为例,页面从简,只分了五个:index.jsp、welcome.jsp、top.jsp、list.jsp、query.jsp。

    需要导入的包如下所示:

    首先在MYSQL创建一个名为school的数据库,数据库内再新建一个学生表tb_student,SQL语句如下所示:

        CREATE DATABASE IF NOT EXISTS school;
        use school;
        CREATE TABLE tb_student (
            sid char(32) PRIMARY KEY,
            sname varchar(30) NOT NULL,
            gender varchar(6) NOT NULL,
            age int(11) NOT NULL,
            cellphone varchar(15) NOT NULL,
            email varchar(40) NOT NULL
        );

    接着配置项目中各层的基本分工,如下所示:

    其中,domain层表示相关的业务数据类,dao层表示对数据库的操作,service层则是业务逻辑层,servlet层则为处理请求的地方。

    分别在domain、dao、service、servlet层创建相关的类文件,如下所示:

    domain

public class Student {
    private String sid;
    private String sname;
    private String gender;
    private int age;
    private String cellphone;
    private String email;

    public String getSid() {
        return sid;
    }

    public void setSid(String sid) {
        this.sid = sid;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        this.age = age;
    }

    public String getCellphone() {
        return cellphone;
    }

    public void setCellphone(String cellphone) {
        this.cellphone = cellphone;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

    dao

public class StudentDao {
    private QueryRunner qr = new ExQueryRunner();
}

    service

public class StudentService {
    private StudentDao studentDao = new StudentDao();
}

    servlet

    在早期中一篇文章中曾经提及过一个BaseServlet,即能够处理多个请求方法的一个Servlet(链接:)。在这里就得令StudentServlet继承这个BaseServlet。因此,StudentServlet代码如下所示:

public class StudentServlet extends BaseServlet {
    private StudentService studentService = new StudentService();
}

    在这里要用到先前写到的几个小工具,分别如下所示:

CommonUtils

public class CommonUtils {
    public static String uuid() {
        return UUID.randomUUID().toString().replace("-", "").toUpperCase();
    }
	
    public static <T> T toBean(Map map, Class<T> clazz) {
        try {
            /*
             * 1. 通过参数clazz创建实例
             * 2. 使用BeanUtils.populate把map的数据封闭到bean中
             */
            T bean = clazz.newInstance();
            ConvertUtils.register(new DateConverter(), java.util.Date.class);
            BeanUtils.populate(bean, map);
            return bean;
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }
}

JdbcUtils

public class JdbcUtils {
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();

    // 事务专用Connection
    private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
	
    public static ComboPooledDataSource getDataSource() {
        return dataSource;
    }

    public static Connection getConnection() throws SQLException {
        // 保证事务的connection对象相同 不等于null说明已经开启事务
        Connection con = tl.get();

        if (con != null) return con;
        return dataSource.getConnection();
	}
	
    public static void beginTransaction() throws SQLException {
        Connection con = tl.get();
        if(con != null) throw new SQLException("事务已开启,不要重复开启"); 
		
        con = getConnection();
        con.setAutoCommit(false);
		
        tl.set(con);
    }
	
    public static void commitTransaction() throws SQLException {
        Connection con = tl.get();		
        if(con == null) throw new SQLException("还未开启事务,无法提交"); 
		
        con.commit();
        con.close();
        /*
         * close方法在这里是关闭连接
         * 如果不设置connection为null的话,在想使用事务时
         * 调用getConnection方法则依旧返回一个已经关闭的连接
         * 设置为null表示事务已经结束
        */
        tl.remove();
    }

    public static void rollbackTransaction() throws SQLException {
        Connection con = tl.get();		
        if(con == null) throw new SQLException("还未开启事务,无法回滚"); 
		
        con.rollback();
        con.close();
        tl.remove();
    }
	
    public static void releaseConnection(Connection connection) throws SQLException {
        Connection con = tl.get();		
        // 如果con为null 说明此时没有事务 关闭
        if(con == null) connection.close();
        // 如果con不为null 说明有事务,那么需要判断参数连接是否与con相等
        if(con != connection) connection.close(); 
    }
}

ExQueryRunner

public class ExQueryRunner extends QueryRunner {

    @Override
    public int[] batch(String sql, Object[][] params) throws SQLException {
        Connection con = JdbcUtils02.getConnection();
        int[] result = super.batch(con, sql, params);
        JdbcUtils02.releaseConnection(con);
        return result;
    }

    @Override
    public <T> T query(String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
        Connection con = JdbcUtils02.getConnection();
        T result = super.query(con, sql, rsh, params);
        JdbcUtils02.releaseConnection(con);
        return result;
    }

    @Override
    public <T> T query(String sql, ResultSetHandler<T> rsh) throws SQLException {
        Connection con = JdbcUtils02.getConnection();
        T result = super.query(con, sql, rsh);
        JdbcUtils02.releaseConnection(con);
        return result;
    }

    @Override
    public int update(String sql) throws SQLException {
        Connection con = JdbcUtils02.getConnection();
        int result = super.update(con, sql);
        JdbcUtils02.releaseConnection(con);
        return result;		
    }
	
    @Override
    public int update(String sql, Object param) throws SQLException {
        Connection con = JdbcUtils02.getConnection();
        int result = super.update(con, sql, param);
        JdbcUtils02.releaseConnection(con);
        return result;
    }

    @Override
    public int update(String sql, Object... params) throws SQLException {
        Connection con = JdbcUtils02.getConnection();
        int result = super.update(con, sql, params);
        JdbcUtils02.releaseConnection(con);
        return result;
    }
}

    在配置完c3p0的各项属性后,最后批量插入200个学生信息,如下所示:

    public void insert() throws SQLException {
        String sql = "insert into tb_student values(?, ?, ?, ?, ?, ?)";
        for (int i = 0; i < 200; i++) {
            Object[] params = { CommonUtils.uuid(), "stu" + i, i % 2 == 0 ? "男" : "女",
                   (int) (5 * Math.random() + 18),"15813554755", 
                        "stu" + i + "@aa.com"
            };
        qr.update(sql, params); }
    }

    配置完基本的东西后,现在开始做分页的处理。

        实现

    首先先实现所有数据的显示,那么就得先理清一下思路:

        点击【查看学生】,请求StudentServlet查询数据库,然后返回一个List<Student>对象,将这个对象保存至Request域当中请求转发到list.jsp。

    根据这个思路,先在StudentServlet中写出如下代码:

    public String findAll(HttpServletRequest request, HttpServletResponse response) {
        List<Student> stuList = studentService.findAll();
        request.setAttribute("stuList", stuList);
        return "f:/list.jsp";
    }

    接下来分别在StudentService以及StudentDao中写下如下代码:

    public List<Student> findAll() {
        return studentDao.findAll();
    }
    public List<Student> findAll() {
        try {
            String sql = "select * from tb_student";			
            return qr.query(sql, new BeanListHandler<Student>(Student.class));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    最后需要到显示的页面list.jsp当中循环遍历出数据。

    <table border="1" width="70%" align="center">
        <tr>
            <th>学生姓名</th>
            <th>性别</th>
            <th>年龄</th>
            <th>手机</th>
            <th>邮箱</th>
        </tr>
		
        <c:forEach items="${requestScope.stuList}" var="stu">
            <tr>
                <td>${stu.sname }</td>
                <td>${stu.gender }</td>
                <td>${stu.age }</td>
                <td>${stu.cellphone }</td>
                <td>${stu.email }</td>
            </tr>
        </c:forEach>

    </table>

    结果如下所示:

    接着再来实现所有数据的分页显示,首先将分页需要的数据封装到一个类中,将其命名为pageBean,其代码如下所示:

public class PageBean<T> {
    private int cp; // 当前页面currentPage
    private int tp; // 总页数totalPage
    private int tr; // 总记录数 totalRecord
    private int ps; // 每页记录数 pageSize
    private List<T> beanList; // 当前页的记录
    private String url; //记录请求参数

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public int getCp() {
        return cp;
    }

    public void setCp(int cp) {
        this.cp = cp;
    }

    public int getTp() {
        int tp = tr / ps;
        return tr%ps==0? tp : tp + 1;
    }

    public int getTr() {
        return tr;
    }

    public void setTr(int tr) {
        this.tr = tr;
    }

    public int getPs() {
        return ps;
    }

    public void setPs(int ps) {
        this.ps = ps;
    }
    
    public List<T> getBeanList() {
        return beanList;
    }

    public void setBeanList(List<T> beanList) {
        this.beanList = beanList;
    }

}

    这里就有几个问题:第一、当前页的页数由谁传递;第二、一页中显示多少条数据在哪里设置;第三、Dao中如何实现返回的是一页的数据。

    第一个问题的解决:当前页的页数由当前显示的页面传递给Servlet,若没有则默认为1;第二个问题的解决:在Servlet中设置;第三个问题的解决:通过SQL语句中的limit字句来实现返回的一页数据。limit在sql中是限制select查询返回的记录行数。

    想清楚了这几个问题,那么接下来先修改StudentServlet:

public String findAll(HttpServletRequest request, HttpServletResponse response) {
        /*
         *     1. 获取当前页码
         *     2. 设置一页显示多少记录
         *     3. 传递两个参数到dao中查询数据库
         *     4. 将返回的PageBean对象保存至Request域中
         * */
        int cp = getCp(request);
        int ps = 10;
        PageBean<Student> pb = studentService.findAll(cp, ps);
        request.setAttribute("pb", pb);
        return "f:/list.jsp";
}

    接着再来修改StudentDao中的sql语句以及返回的对象。

public PageBean<Student> findAll(int cp, int ps) {
        try {
            //创建PageBean对象并且保存当前页码以及一页显示的记录数
            PageBean<Student> pb = new PageBean<Student>();
            pb.setCp(cp);
            pb.setPs(ps);
						
            //设置总记录数,从数据库当中获取
            String sql = "select count(*) from tb_student";
            Number num = (Number) qr.query(sql, new ScalarHandler());
            pb.setTr(num.intValue());
            sql = "select * from tb_student limit ?, ?";
            Object[] params = {(cp-1)*ps, ps};
            pb.setBeanList(qr.query(sql, new BeanListHandler<Student>(Student.class), params));
            //设置完四大属性返回PageBean对象
            return pb;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
}

    最后修改list.jsp当中的显示,这里就有一个问题:如何动态地显示分页页码列表?其实很简单,只需要通过当前页的页码来定出页码列表。因此,我们只需要通过当前页页码currentPage来计算页码的开头begin和页码的末尾end。这里就需要有一些公式来达到这个效果,如下所示:

    '''
        前提:最多显示10个页码、当前页在页码列表的位置为6

        如果总页数<=10(列表长度),那么begin=1, end = 总页数;
        否则使用公式计算:begin = pc - 5;end = pc + 4;
        假设头溢出:当begin < 1时,令begin = 1;
        假设尾溢出:当begin > 总页数时, 令end = 总页数
    '''

    根据这个公式以及上述的思路,list.jsp的完整代码如下所示:

<center>
        第${pb.cp }页/共${pb.tp }页
        <a href="<c:url value='/StudentServlet?method=findAll'/>&cp=1">首页</a>
        <c:if test="${pb.cp > 1 }">
            <a href="<c:url value='/StudentServlet?method=findAll'/>&cp=${pb.cp-1 }">上一页</a>
        </c:if>
		
        <c:choose>
            <c:when test="${pb.tp <= 10 }">
                <c:set var="begin" value = "1" />
                <c:set var="end" value = "${pb.tp }" />
            </c:when>
            <c:otherwise>
                <c:set var="begin" value = "${pb.cp - 5 }" />
                <c:set var="end" value = "${pb.cp + 4 }" />
                <c:if test="${begin < 1 }">
                    <c:set var="begin" value = "1" />
                    <c:set var="end" value = "10" />			
                </c:if>
                <c:if test="${end > pb.tp }">
                    <c:set var="begin" value = "${pb.tp - 9 }" />
                    <c:set var="end" value = "${pb.tp }" />			
                </c:if>
            </c:otherwise>
        </c:choose>

        <c:forEach var="i" begin="${begin }" end="${end }">
            <c:choose>
                <c:when test="${i eq pb.cp }">
                    [${i }]
                </c:when>
                <c:otherwise>
                    <a href="<c:url value='/StudentServlet?method=findAll'/>&cp=${i }">[${i }]</a>
                </c:otherwise>
            </c:choose>
        </c:forEach>
		
        <c:if test="${pb.cp < pb.tp }">
            <a href="<c:url value='/StudentServlet?method=findAll'/>&cp=${pb.cp+1 }">上一页</a>
        </c:if>
	
        <a href="<c:url value='/StudentServlet?method=findAll'/>&cp=${pb.tp }">尾页</a>
    </center>
	

    结果呈现:

    最基本的全部数据分页显示就此完成。接下来需要通过查询搜索来显示分页,这里先抛出一个问题以供思考:输入的查询条件如何保存,以至于第二页甚至后面的页码里显示的都是符合条件的数据?

    先理清一个思路:点击【查询】按钮,将输入的表单数据传递到Servlet,通过StudentDao查询符合条件的记录,并将所有符合条件的记录封装到一个List中并保存至pageBean内,返回这个pageBean对象,最后将这个pageBean对象保存至Request域当中转发到list.jsp。

    那么到底如何保存这个查询条件呢?通过将表单的上传方式改成get即可完成这样的效果,即在url中记录请求参数。这就是pageBean当中的最后一个属性url的意义,也即分页中组成里的url的意义。

    根据这个思路,先修改StudentServlet:

    // 截取url : 项目名/Servlet路径?参数字符串
    public String getUrl(HttpServletRequest request) {
        String contextPath = request.getContextPath();
        String servletPath = request.getServletPath();
        String queryString = request.getQueryString();
		
        if(queryString.contains("&cp=")) {
            int index = queryString.lastIndexOf("&cp=");
            queryString = queryString.substring(0, index);
        }
		
        return contextPath + servletPath + "?" + queryString;
    }
	
    // 获取当前页码
    public int getCp(HttpServletRequest request) {
        String value = request.getParameter("cp");
        if (value == null || value.trim().isEmpty())
            return 1;
        return Integer.parseInt(value);
    }
	
    private Student encoding(Student stu) throws UnsupportedEncodingException {
        String sname = stu.getSname();
        String gender = stu.getGender();
        String cellphone = stu.getCellphone();
        String email = stu.getEmail();
		
        if(sname != null && !sname.trim().isEmpty()) {
            sname = new String(sname.getBytes("ISO-8859-1"), "UTF-8");
            stu.setSname(sname);
        }
        if(gender != null && !gender.trim().isEmpty()) {
            gender = new String(gender.getBytes("ISO-8859-1"), "UTF-8");
            stu.setGender(gender);
        }
        if(cellphone != null && !cellphone.trim().isEmpty()) {
            cellphone = new String(cellphone.getBytes("ISO-8859-1"), "UTF-8");
            stu.setCellphone(cellphone);
        }
        if(email != null && !email.trim().isEmpty()) {
            email = new String(email.getBytes("ISO-8859-1"), "UTF-8");
            stu.setEmail(email);
        }
        
        return stu;
    }

    public String query(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        // 将表单内的所有参数封装至Student内
        Student criteria = CommonUtils.toBean(request.getParameterMap(), Student.class);
        // 由于是get请求 需要处理乱码问题
        criteria = encoding(criteria);
        int cp = getCp(request);
        int ps = 10;
        PageBean<Student> pb = studentService.query(criteria, cp, ps);
        pb.setUrl(getUrl(request));
        request.setAttribute("pb", pb);
        return "f:/list.jsp";
    }

    再修改StudentDao:

public PageBean<Student> query(Student criteria, int cp, int ps) {
        try {
            PageBean<Student> pb = new PageBean<Student>();
            pb.setCp(cp);
            pb.setPs(ps);
            /*
             * 	条件查询并非全满足查询,只要满足一个即可
             *  除了性别以外的查询都可以是模糊查询
             *  除了获取总记录数tr以外,查询记录也需要用到where判断子句
             *  因此,将where子句单独拿出来以重用
             * */
            // 保存参数用
            List<Object> params = new ArrayList<Object>();		
            StringBuilder sql = new StringBuilder("select count(*) from tb_student");			
            StringBuilder whereSql = new StringBuilder(" where 1 = 1");
			
            String sname  = criteria.getSname();
            if(sname != null && !sname.trim().isEmpty()) {
                whereSql.append(" and sname like ?");
                params.add("%" + sname + "%");
            }
            String gender = criteria.getGender();
            if(gender != null && !gender.trim().isEmpty()) {
                whereSql.append(" and gender = ?");
                params.add(gender);
            }
            String phone = criteria.getCellphone();
            if(phone != null && !phone.trim().isEmpty()) {
                whereSql.append(" and cellphone like ?");
                params.add("%" + phone + "%");
            }
            String email = criteria.getEmail();
            if(email != null && !email.trim().isEmpty()) {
                whereSql.append(" and email like ?");
                params.add("%" + email + "%");
            }
            Number num = (Number) qr.query(sql.append(whereSql).toString(), 
                    new ScalarHandler(), params.toArray());
            pb.setTr(num.intValue());

            sql = new StringBuilder("select * from tb_student");
            StringBuilder limitSql = new StringBuilder(" limit ?, ?");
            params.add((cp-1)*ps);
            params.add(ps);
			
            pb.setBeanList(qr.query(sql.append(whereSql).append(limitSql).toString(),
                     new BeanListHandler<Student>(Student.class), 
                            params.toArray()));
			
            return pb;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    最后修改list.jsp页面:

    <a href="${pb.url }&cp=1">首页</a>
    <a href="${pb.url }&cp=${pb.cp-1 }">上一页</a>
    <a href="${pb.url }&cp=${i }">[${i }]</a>
    <a href="${pb.url }&cp=${pb.cp+1 }">上一页</a>
    <a href="${pb.url }&cp=${pb.tp }">尾页</a>

    只查询为性别为男的学生,结果显示如下:

后话

    '''
        分页暂时只讲通过SQL中的limit子句完成的部分,除此之外还有过滤器以及框架直接实现分页效果。
        下一章讲监听器和过滤器的内容
    '''

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值