动力节点首页 全国咨询热线:400-8080-105

绑定手机号,登录
手机号

验证码

微信登录
手机号登录
手机号

验证码

微信登录与注册
微信扫码登录与注册

扫码关注微信公众号完成登录与注册
手机号登录
首页 > 文章

实现JavaWeb分页的四种方法

07-09 09:03 704浏览
举报 T字号
  • 大字
  • 中字
  • 小字

1.借助数组进行分页

原理:进行数据库查询操作时,获取到数据库中所有满足条件的记录,保存在应用的临时数组中,再通过List的subList方法,获取到满足条件的所有记录。

实现:

首先在dao层,创建StudentMapper接口,用于对数据库的操作。在接口中定义通过数组分页的查询方法,如下所示:

List<Student> queryStudentsByArray();

方法很简单,就是获取所有的数据,通过list接收后进行分页操作。

创建StudentMapper.xml文件,编写查询的sql语句:

 <select id="queryStudentsByArray" resultMap="studentmapper">
   select * from student
 </select>

可以看出再编写sql语句的时候,我们并没有作任何分页的相关操作。这里是查询到所有的学生信息。

接下来在service层获取数据并且进行分页实现:

定义IStuService接口,并且定义分页方法:

List <Student>queryStudentsByArray(int currPage, int pageSize);

通过接收currPage参数表示显示第几页的数据,pageSize表示每页显示的数据条数。

创建IStuService接口实现类StuServiceIml对方法进行实现,对获取到的数组通过currPage和pageSize进行分页:

@Override
public List<Student> queryStudentsByArray(int currPage, int pageSize) {
    List<Student> students = studentMapper.queryStudentsByArray();
    // 从第几条数据开始
    int firstIndex = (currPage - 1) * pageSize;
    // 到第几条数据结束
    int lastIndex = currPage * pageSize;
    return students.subList(firstIndex, lastIndex);
}

通过subList方法,获取到两个索引间的所有数据。

缺点:数据库查询并返回所有的数据,而我们需要的只是极少数符合要求的数据。当数据量少时,还可以接受。当数据库数据量过大时,每次查询对数据库和程序的性能都会产生极大的影响。

2.借助Sql语句进行分页

在了解到通过数组分页的缺陷后,我们发现不能每次都对数据库中的所有数据都检索。然后在程序中对获取到的大量数据进行二次操作,这样对空间和性能都是极大的损耗。所以我们希望能直接在数据库语言中只检索符合条件的记录,不需要在通过程序对其作处理。这时,Sql语句分页技术横空出世。

实现:通过sql语句实现分页也是非常简单的,只是需要改变我们查询的语句就能实现了,即在sql语句后面添加limit分页语句。

首先还是在StudentMapper接口中添加sql语句查询的方法,如下:

List<Student> queryStudentsBySql(Map<String,Object> data);

然后在StudentMapper.xml文件中编写sql语句通过limiy关键字进行分页:

 <select id="queryStudentsBySql" parameterType="map" resultMap="studentmapper">
     select * from student limit #{currIndex} , #{pageSize}
 </select>

接下来还是在IStuService接口中定义方法,并且在StuServiceIml中对sql分页实现。

sql分页语句如下:

select * from table limit index, pageSize;

所以在service中计算出currIndex:要开始查询的第一条记录的索引。

结果:

从输出结果可以看出和数组分页的结果是一致的,因此sql语句的分页也是没问题的。

缺点:虽然这里实现了按需查找,每次检索得到的是指定的数据。但是每次在分页的时候都需要去编写limit语句,很冗余。而且不方便统一管理,维护性较差。所以我们希望能够有一种更方便的分页实现。

3.拦截器分页

上面提到的数组分页和sql语句分页都不是我们今天讲解的重点,今天需要实现的是利用拦截器达到分页的效果。自定义拦截器实现了拦截所有以ByPage结尾的查询语句,并且利用获取到的分页相关参数统一在sql语句后面加上limit分页的相关语句,一劳永逸。不再需要在每个语句中单独去配置分页相关的参数了。

首先我们看一下拦截器的具体实现,在这里我们需要拦截所有以ByPage结尾的所有查询语句,因此要使用该拦截器实现分页功能,那么再定义名称的时候需要满足它拦截的规则(以ByPage结尾),如下所示:

package com.cbg.interceptor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.sql.Connection;
import java.util.Map;
import java.util.Properties;
/**
* @Intercepts 说明是一个拦截器
* @Signature 拦截器的签名
* type 拦截的类型 四大对象之一( Executor,ResultSetHandler,ParameterHandler,StatementHandler)
* method 拦截的方法
* args 参数
*/
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MyPageInterceptor implements Interceptor {
//每页显示的条目数
private int pageSize;
//当前现实的页数
private int currPage;
private String dbType;
@Override
public Object intercept(Invocation invocation) throws Throwable {
    //获取StatementHandler,默认是RoutingStatementHandler
    StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
    //获取statementHandler包装类
    MetaObject MetaObjectHandler = SystemMetaObject.forObject(statementHandler);
    //分离代理对象链
    while (MetaObjectHandler.hasGetter("h")) {
        Object obj = MetaObjectHandler.getValue("h");
        MetaObjectHandler = SystemMetaObject.forObject(obj);
    }
    while (MetaObjectHandler.hasGetter("target")) {
        Object obj = MetaObjectHandler.getValue("target");
        MetaObjectHandler = SystemMetaObject.forObject(obj);
    }
    //获取连接对象
    //Connection connection = (Connection) invocation.getArgs()[0];
    //object.getValue("delegate"); 获取StatementHandler的实现类
    //获取查询接口映射的相关信息
    MappedStatement mappedStatement = (MappedStatement) MetaObjectHandler.getValue("delegate.mappedStatement");
    String mapId = mappedStatement.getId();
    //statementHandler.getBoundSql().getParameterObject();
    //拦截以.ByPage结尾的请求,分页功能的统一实现
    if (mapId.matches(".+ByPage$")) {
        //获取进行数据库操作时管理参数的handler
        ParameterHandler parameterHandler = (ParameterHandler) MetaObjectHandler.getValue("delegate.parameterHandler");
        //获取请求时的参数
        Map<String, Object> paraObject = (Map<String, Object>) parameterHandler.getParameterObject();
        //也可以这样获取
        //paraObject = (Map<String, Object>) statementHandler.getBoundSql().getParameterObject();
        //参数名称和在service中设置到map中的名称一致
        currPage = (int) paraObject.get("currPage");
        pageSize = (int) paraObject.get("pageSize");
        String sql = (String) MetaObjectHandler.getValue("delegate.boundSql.sql");
        //也可以通过statementHandler直接获取
        //sql = statementHandler.getBoundSql().getSql();
        //构建分页功能的sql语句
        String limitSql;
        sql = sql.trim();
        limitSql = sql + " limit " + (currPage - 1) * pageSize + "," + pageSize;
        //将构建完成的分页sql语句赋值个体'delegate.boundSql.sql',偷天换日
        MetaObjectHandler.setValue("delegate.boundSql.sql", limitSql);
    }
    //调用原对象的方法,进入责任链的下一级
    return invocation.proceed();
}
//获取代理对象
@Override
    public Object plugin(Object o) {
    //生成object对象的动态代理对象
    return Plugin.wrap(o, this);
}
//设置代理对象的参数
@Override
    public void setProperties(Properties properties) {
        //如果项目中分页的pageSize是统一的,也可以在这里统一配置和获取,这样就不用每次请求都传递pageSize参数了。参数是在配置拦截器时配置的。
        String limit1 = properties.getProperty("limit", "10");
        this.pageSize = Integer.valueOf(limit1);
        this.dbType = properties.getProperty("dbType", "mysql");
    }
}

上面即是JavaWeb拦截器功能的实现,在intercept方法中获取到select标签和sql语句的相关信息,拦截所有以ByPage结尾的select查询,并且统一在查询语句后面添加limit分页的相关语句,统一实现分页功能。

重点详解:

StatementHandler是一个接口,而我们在代码中通过StatementHandler statementHandler = (StatementHandler) invocation.getTarget();获取到的是StatementHandler默认的实现类RoutingStatementHandler。而RoutingStatementHandler只是一个中间代理,他不会提供具体的方法。那你可能会纳闷了,拦截器中基本上是依赖statementHandler获取各种对象和属性的,没有具体属性和方法怎么行??接着看下面代码:

private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    switch(RoutingStatementHandler.SyntheticClass_1.$SwitchMap$org$apache$ibatis$mapping$StatementType[ms.getStatementType().ordinal()]) {
        case 1:
        this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
        case 2:
        this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
        case 3:
        this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
        default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
}

原来它是通过不同的MappedStatement创建不同的StatementHandler实现类对象处理不同的情况。这里的到的StatementHandler实现类才是真正服务的。看到这里,你可能就会明白MappedStatement mappedStatement = (MappedStatement) MetaObjectHandler.getValue("delegate.mappedStatement");中delegate的来源了吧。至于为什么要这么去获取,后面我们会说道。

拿到statementHandler后,我们会通过MetaObject MetaObjectHandler = SystemMetaObject.forObject(statementHandler);去获取它的包装对象,通过包装对象去获取各种服务。

MetaObject:mybatis的一个工具类,方便我们有效的读取或修改一些重要对象的属性。四大对象(ResultSetHandler,ParameterHandler,Executor和statementHandler)提供的公共方法很少,要想直接获取里面属性的值很困难,但是可以通过MetaObject利用一些技术(内部反射实现)很轻松的读取或修改里面的数据。

动力节点在线课程涵盖零基础入门,高级进阶,在职提升三大主力内容,覆盖Java从入门到就业提升的全体系学习内容。全部Java视频免费观看,相关学习资料免费下载!对于火爆技术,每周一定时更新!如果想了解更多相关技术,可以到动力节点在线免费观看JavaWeb视频教程学习哦!

0人推荐
共同学习,写下你的评论
0条评论
代码小兵988
程序员代码小兵988

67篇文章贡献228982字

相关课程 更多>

作者相关文章更多>

推荐相关文章更多>

Java面试题及答案整理

提枪策马乘胜追击04-21 20:01

Spring常见面试题

代码小兵92504-17 16:07

Java零基础实战项目——五子棋

代码小兵98804-25 13:57

Java string类详解

杨晶珍05-11 14:54

6道经典算法面试题

杨晶珍05-12 16:39

发评论

举报

0/150

取消