分页原理及方式

分页的三种方式

转自:https://blog.csdn.net/liuyinfei_java/article/details/79211773

 

2018年01月30日 22:34:53 阅读数:327

实现方式 优点 缺点 适用场景
subList 简单、易用 效率低 无法按需批量获取数据
SQL语句 简单、直接、效率高 数据库兼容性差 不要求数据库兼容
Hibernate框架 面向对象,兼容性强 复杂查询性能低 兼容不同数据库

一.借助数组进行分页

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

  • 实现:

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

List

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

 

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

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

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

List queryStudentsByArray(int currPage, int pageSize);

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

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

 
  1. @Override

  2. public List queryStudentsByArray(int currPage, int pageSize) {

  3. List students = studentMapper.queryStudentsByArray();

  4. // 从第几条数据开始

  5. int firstIndex = (currPage - 1) * pageSize;

  6. // 到第几条数据结束

  7. int lastIndex = currPage * pageSize;

  8. return students.subList(firstIndex, lastIndex);

  9. }

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

最后在controller中创建测试方法:

 
  1. @ResponseBody

  2. @RequestMapping("/student/array/{currPage}/{pageSize}")

  3. public List getStudentByArray(@PathVariable("currPage") int currPage, @PathVariable("pageSize") int pageSize) {

  4. List student = StuServiceIml.queryStudentsByArray(currPage, pageSize);

  5. return student;

  6. }

通过用户传入的currPage和pageSize获取指定数据。


二.借助Sql语句进行分页

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

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

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

List

 

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

 
  1. List queryStudentsBySql(int currPage, int pageSize);

  2. @Override

  3. public List queryStudentsBySql(int currPage, int pageSize) {

  4. Map data = new HashedMap();

  5. data.put("currIndex", (currPage-1)*pageSize);

  6. data.put("pageSize", pageSize);

  7. return studentMapper.queryStudentsBySql(data);

  8. }

sql分页语句如下:select * from table limit index, pageSize;

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


三.拦截器分页

上面提到的数组分页和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;

 
  1. private String dbType;

  2.  
  3.  
  4. @Override

  5. public Object intercept(Invocation invocation) throws Throwable {

  6. //获取StatementHandler,默认是RoutingStatementHandler

  7. StatementHandler statementHandler = (StatementHandler) invocation.getTarget();

  8. //获取statementHandler包装类

  9. MetaObject MetaObjectHandler = SystemMetaObject.forObject(statementHandler);

  10.  
  11. //分离代理对象链

  12. while (MetaObjectHandler.hasGetter("h")) {

  13. Object obj = MetaObjectHandler.getValue("h");

  14. MetaObjectHandler = SystemMetaObject.forObject(obj);

  15. }

  16.  
  17. while (MetaObjectHandler.hasGetter("target")) {

  18. Object obj = MetaObjectHandler.getValue("target");

  19. MetaObjectHandler = SystemMetaObject.forObject(obj);

  20. }

  21.  
  22. //获取连接对象

  23. //Connection connection = (Connection) invocation.getArgs()[0];

  24.  
  25.  
  26. //object.getValue("delegate"); 获取StatementHandler的实现类

  27.  
  28. //获取查询接口映射的相关信息

  29. MappedStatement mappedStatement = (MappedStatement) MetaObjectHandler.getValue("delegate.mappedStatement");

  30. String mapId = mappedStatement.getId();

  31.  
  32. //statementHandler.getBoundSql().getParameterObject();

  33.  
  34. //拦截以.ByPage结尾的请求,分页功能的统一实现

  35. if (mapId.matches(".+ByPage$")) {

  36. //获取进行数据库操作时管理参数的handler

  37. ParameterHandler parameterHandler = (ParameterHandler) MetaObjectHandler.getValue("delegate.parameterHandler");

  38. //获取请求时的参数

  39. Map paraObject = (Map) parameterHandler.getParameterObject();

  40. //也可以这样获取

  41. //paraObject = (Map) statementHandler.getBoundSql().getParameterObject();

  42.  
  43. //参数名称和在service中设置到map中的名称一致

  44. currPage = (int) paraObject.get("currPage");

  45. pageSize = (int) paraObject.get("pageSize");

  46.  
  47. String sql = (String) MetaObjectHandler.getValue("delegate.boundSql.sql");

  48. //也可以通过statementHandler直接获取

  49. //sql = statementHandler.getBoundSql().getSql();

  50.  
  51. //构建分页功能的sql语句

  52. String limitSql;

  53. sql = sql.trim();

  54. limitSql = sql + " limit " + (currPage - 1) * pageSize + "," + pageSize;

  55.  
  56. //将构建完成的分页sql语句赋值个体'delegate.boundSql.sql',偷天换日

  57. MetaObjectHandler.setValue("delegate.boundSql.sql", limitSql);

  58. }

  59.  
  60. return invocation.proceed();

  61. }

  62.  
  63.  
  64. //获取代理对象

  65. @Override

  66. public Object plugin(Object o) {

  67. return Plugin.wrap(o, this);

  68. }

  69.  
  70. //设置代理对象的参数

  71. @Override

  72. public void setProperties(Properties properties) {

//如果项目中分页的pageSize是统一的,也可以在这里统一配置和获取,这样就不用每次请求都传递pageSize参数了。参数是在配置拦截器时配置的。
String limit1 = properties.getProperty("limit", "10");
this.pageSize = Integer.valueOf(limit1);
this.dbType = properties.getProperty("dbType", "mysql");
}
}
上面即是拦截器功能的实现,在intercept方法中获取到select标签和sql语句的相关信息,拦截所有以ByPage结尾的select查询,并且统一在查询语句后面添加limit分页的相关语句,统一实现分页功能。

重点详解:

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

private final StatementHandler delegate;

 
  1. public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

  2. switch(RoutingStatementHandler.SyntheticClass_1.$SwitchMap$org$apache$ibatis$mapping$StatementType[ms.getStatementType().ordinal()]) {

  3. case 1:

  4. this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);

  5. break;

  6. case 2:

  7. this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);

  8. break;

  9. case 3:

  10. this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);

  11. break;

  12. default:

  13. throw new ExecutorException("Unknown statement type: " + ms.getStatementType());

  14. }

  15.  
  16. }

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

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

接下来说说:MappedStatement mappedStatement = (MappedStatement) MetaObjectHandler.getValue("delegate.mappedStatement");

上面提到为什么要这么去获取MappedStatement对象??在RoutingStatementHandler中delegate是私有的(private final StatementHandler delegate;),有没有共有的方法去获取。所以这里只有通过反射来获取啦。

MappedStatement是保存了xxMapper.xml中一个sql语句节点的所有信息的包装类,可以通过它获取到节点中的所有信息。在示例中我们拿到了id值,也就是方法的名称,通过名称区拦截所有需要分页的请求。

通过StatementHandler的包装类,不光能拿到MappedStatement,还可以拿到下面的数据:

 
  1. public abstract class BaseStatementHandler implements StatementHandler {

  2. protected final Configuration configuration;

  3. protected final ObjectFactory objectFactory;

  4. protected final TypeHandlerRegistry typeHandlerRegistry;

  5. protected final ResultSetHandler resultSetHandler;

  6. protected final ParameterHandler parameterHandler;

  7. protected final Executor executor;

  8. protected final MappedStatement mappedStatement;

  9. protected final RowBounds rowBounds;

  10. protected BoundSql boundSql;

  11.  
  12. protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

  13. this.configuration = mappedStatement.getConfiguration();

  14. this.executor = executor;

  15. this.mappedStatement = mappedStatement;

  16. this.rowBounds = rowBounds;

  17. this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();

  18. this.objectFactory = this.configuration.getObjectFactory();

  19. if(boundSql == null) {

  20. this.generateKeys(parameterObject);

  21. boundSql = mappedStatement.getBoundSql(parameterObject);

  22. }

  23.  
  24. this.boundSql = boundSql;

  25. this.parameterHandler = this.configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);

  26. this.resultSetHandler = this.configuration.newResultSetHandler(executor, mappedStatement, rowBounds, this.parameterHandler, resultHandler, boundSql);

  27. }

上面的所有数据都可以通过反射拿到。

几个重要的参数:
Configuration:所有配置的相关信息。
ResultSetHandler:用于拦截执行结果的组装。
ParameterHandler:拦截执行Sql的参数的组装。
Executor:执行Sql的全过程,包括组装参数、组装结果和执行Sql的过程。
BoundSql:执行的Sql的相关信息。

接下来我们通过如下代码拿到请求时的map对象(反射)。

 
  1. //获取进行数据库操作时管理参数的handler

  2. ParameterHandler parameterHandler = (ParameterHandler) MetaObjectHandler.getValue("delegate.parameterHandler");

  3. //获取请求时的参数

  4. Map paraObject = (Map) parameterHandler.getParameterObject();

  5. //也可以这样获取

  6. //paraObject = (Map) statementHandler.getBoundSql().getParameterObject();

  7. 拿到我们需要的currPage和pageSize参数后,就是组装分页查询的sql语句’limitSql‘了。

  8.  
  9. 最后通过MetaObjectHandler.setValue("delegate.boundSql.sql", limitSql);将原始的sql语句替换成我们新的分页语句,完成偷天换日的功能,接下来让代码继续执行。

  10.  
  11. //编写好拦截器后,需要注册到项目中,才能发挥它的作用。在mybatis的配置文件中,添加如下代码:

  12.  

如上所示,还能在里面配置一些属性,在拦截器的setProperties方法中可以获取配置好的属性值。如项目分页的pageSize参数的值固定,我们就可以配置在这里了,以后就不需要每次传入pageSize了,读取方式如下:

 
  1. //读取配置的代理对象的参数

  2. @Override

  3. public void setProperties(Properties properties) {

  4. String limit1 = properties.getProperty("limit", "10");

  5. this.pageSize = Integer.valueOf(limit1);

  6. this.dbType = properties.getProperty("dbType", "mysql");

  7. }

到这里,有关拦截器的相关知识就讲解的差不多了,接下来就需要测试,是否我们这样写真的有效??

首先还是添加dao层的方法和xml文件的sql语句配置,注意项目中拦截的是以ByPage结尾的请求,所以在这里,我们的方法名称也以此结尾:

方法

List queryStudentsByPage(Map data);

xml文件的select语句

 

可以看出,这里我们就不需要再去手动配置分页语句了。

接下来是service层的接口编写和实现方法:

方法:

List queryStudentsByPage(int currPage,int pageSize);

实现:

 
  1. @Override

  2. public List queryStudentsByPage(int currPage, int pageSize) {

  3. Map data = new HashedMap();

  4. data.put("currPage", currPage);

  5. data.put("pageSize", pageSize);

  6. return studentMapper.queryStudentsByPage(data);

  7. }

这里我们虽然传入了currPage和pageSize两个参数,但是在sql的xml文件中并没有使用,直接在拦截器中获取到统一使用。

最后编写controller的测试代码:

 
  1. @ResponseBody

  2. @RequestMapping("/student/page/{currPage}/{pageSize}")

  3. public List getStudentByPage(@PathVariable("currPage") int currPage, @PathVariable("pageSize") int pageSize) {

  4. List student = StuServiceIml.queryStudentsByPage(currPage, pageSize);

  5. return student;

  6. }

你可能感兴趣的:(java,web学习)