mybatis的4种分页方式

原文见:https://blog.csdn.net/chenbaige/article/details/70846902

今天我们就来进行基于mybatis和MySql进行分页功能的实现。常见的数据分页有哪几种实现??基于数组的分页实现?基于sql语句的分页实现?还是通过拦截器进行数据分页功能?还是通过RowBounds参数进行物理分页?几种都是常用的分页实现原理,接下来就按照数组、sql语句,拦截器和RowBounds的方式介绍分页功能。

一.数组分页

原理:进行数据库查询操作时,获取到数据库中所有满足条件的记录,保存在应用的临时数组中,再通过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方法,获取到两个索引间的所有数据。

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

            @ResponseBody
              @RequestMapping("/student/array/{currPage}/{pageSize}")
              public List<Student> getStudentByArray(@PathVariable("currPage") int currPage, @PathVariable("pageSize") int pageSize) {
                  List<Student> student = StuServiceIml.queryStudentsByArray(currPage, pageSize);
                  return student;
              }
           
           

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

            测试:

            首先我们来获取再没实现分页效果前获取到的所有数据,如下所示:
            这里写图片描述

            接下来在浏览器输入http://localhost:8080/student/student/array/1/2测试实现了分页后的数据。获取第一页的数据,每页显示两条数据。

            结果如下:
            这里写图片描述
            输出的是指定的从第0-2条数据,可见我们通过数组分页的功能是成功的。(这里因为用到了关联查询,所以看起来数据可能比较多)

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

            二.借助Sql语句进行分页

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

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

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

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

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

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

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

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

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

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

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

                public abstract class BaseStatementHandler implements StatementHandler {
                protected final Configuration configuration;
                protected final ObjectFactory objectFactory;
                protected final TypeHandlerRegistry typeHandlerRegistry;
                protected final ResultSetHandler resultSetHandler;
                protected final ParameterHandler parameterHandler;
                protected final Executor executor;
                protected final MappedStatement mappedStatement;
                protected final RowBounds rowBounds;
                protected BoundSql boundSql;
            
                protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
                    this.configuration = mappedStatement.getConfiguration();
                    this.executor = executor;
                    this.mappedStatement = mappedStatement;
                    this.rowBounds = rowBounds;
                    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
                    this.objectFactory = this.configuration.getObjectFactory();
                    if(boundSql == null) {
                        this.generateKeys(parameterObject);
                        boundSql = mappedStatement.getBoundSql(parameterObject);
                    }
            
                    this.boundSql = boundSql;
                    this.parameterHandler = this.configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
                    this.resultSetHandler = this.configuration.newResultSetHandler(executor, mappedStatement, rowBounds, this.parameterHandler, resultHandler, boundSql);
                }
             
             

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

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

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

               //获取进行数据库操作时管理参数的handler
                          ParameterHandler parameterHandler = (ParameterHandler) MetaObjectHandler.getValue("delegate.parameterHandler");
                          //获取请求时的参数
                          Map<String, Object> paraObject = (Map<String, Object>) parameterHandler.getParameterObject();
                          //也可以这样获取
                          //paraObject = (Map<String, Object>) statementHandler.getBoundSql().getParameterObject();
               
               

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

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

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

                    <plugins>
                        <plugin interceptor="com.cbg.interceptor.MyPageInterceptor">
                            <property name="limit" value="10"/>
                            <property name="dbType" value="mysql"/>
                        </plugin>
                    </plugins>
                 
                 

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

                   //读取配置的代理对象的参数
                      @Override
                      public void setProperties(Properties properties) {
                          String limit1 = properties.getProperty("limit", "10");
                          this.pageSize = Integer.valueOf(limit1);
                          this.dbType = properties.getProperty("dbType", "mysql");
                      }
                   
                   

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

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

                    方法
                    List<Student> queryStudentsByPage(Map<String,Object> data);
                    
                    xml文件的select语句
                        <select id="queryStudentsByPage" parameterType="map" resultMap="studentmapper">
                            select * from student
                        </select>

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

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

                    方法:
                    List<Student> queryStudentsByPage(int currPage,int pageSize);
                    
                    实现:
                     @Override
                        public List<Student> queryStudentsByPage(int currPage, int pageSize) {
                            Map<String, Object> data = new HashedMap();
                            data.put("currPage", currPage);
                            data.put("pageSize", pageSize);
                            return studentMapper.queryStudentsByPage(data);
                        }
                     
                     

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

                      最后编写controller的测试代码:

                       @ResponseBody
                          @RequestMapping("/student/page/{currPage}/{pageSize}")
                          public List<Student> getStudentByPage(@PathVariable("currPage") int currPage, @PathVariable("pageSize") int pageSize) {
                              List<Student> student = StuServiceIml.queryStudentsByPage(currPage, pageSize);
                              return student;
                          }
                      
                       
                       

                        测试:
                        在浏览器输入:http://localhost:8080/student/student/page/1/2

                        结果:
                        这里写图片描述
                        可见和上面两种分页的效果是一样的。

                        四.RowBounds实现分页

                        原理:通过RowBounds实现分页和通过数组方式分页原理差不多,都是一次获取所有符合条件的数据,然后在内存中对大数据进行操作,实现分页效果。只是数组分页需要我们自己去实现分页逻辑,这里更加简化而已。

                        存在问题:一次性从数据库获取的数据可能会很多,对内存的消耗很大,可能导师性能变差,甚至引发内存溢出。

                        适用场景:在数据量很大的情况下,建议还是适用拦截器实现分页效果。RowBounds建议在数据量相对较小的情况下使用。

                        简单介绍:这是代码实现上最简单的一种分页方式,只需要在dao层接口中要实现分页的方法中加入RowBounds参数,然后在service层通过offset(从第几行开始读取数据,默认值为0)和limit(要显示的记录条数,默认为java允许的最大整数:2147483647)两个参数构建出RowBounds对象,在调用dao层方法的时,将构造好的RowBounds传进去就能轻松实现分页效果了。

                        具体操作如下:

                        dao层接口方法:

                        //加入RowBounds参数
                        public List<UserBean> queryUsersByPage(String userName, RowBounds rowBounds);
                         
                         
                        • 1
                        • 2

                        然后在service层构建RowBounds,调用dao层方法:

                          @Override
                            @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.SUPPORTS)
                            public List<RoleBean> queryRolesByPage(String roleName, int start, int limit) {
                                return roleDao.queryRolesByPage(roleName, new RowBounds(start, limit));
                            }

                        RowBounds就是一个封装了offset和limit简单类,如下所示:

                        public class RowBounds {
                            public static final int NO_ROW_OFFSET = 0;
                            public static final int NO_ROW_LIMIT = 2147483647;
                            public static final RowBounds DEFAULT = new RowBounds();
                            private int offset;
                            private int limit;
                        
                            public RowBounds() {
                                this.offset = 0;
                                this.limit = 2147483647;
                            }
                        
                            public RowBounds(int offset, int limit) {
                                this.offset = offset;
                                this.limit = limit;
                            }
                        
                            public int getOffset() {
                                return this.offset;
                            }
                        
                            public int getLimit() {
                                return this.limit;
                            }
                        }
                        
                         
                         

                          只需要这两步操作,就能轻松实现分页效果了,是不是很神奇。但却不简单,内部是怎么实现的??给大家提供一个简单的思路:RowBounds分页简单原理

                          结论:从上面四种sql分页的实现方式可以看出,通过RowBounds实现是最简便的,但是通过拦截器的实现方式是最优的方案。只需一次编写,所有的分页方法共同使用,还可以避免多次配置时的出错机率,需要修改时也只需要修改这一个文件,一劳永逸。而且是我们自己实现的,便于我们去控制和增加一些逻辑处理,使我们在外层更简单的使用。同时也不会出现数组分页和RowBounds分页导致的性能问题。当然,具体情况可以采取不同的解决方案。数据量小时,RowBounds不失为一种好办法。但是数据量大时,实现拦截器就很有必要了。

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

                          请填写红包祝福语或标题

                          红包个数最小为10个

                          红包金额最低5元

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

                          抵扣说明:

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

                          余额充值