mybatis分页的方式:1、借助数组进行分页,首先查询出全部数据,然后再list中截取需要的部分。2、借助sql语句进行分页,在sql语句后面添加limit分页语句即可。3、利用拦截器分页,通过拦截器给sql语句末尾加上limit语句来分页查询。4、利用rowbounds实现分页,需要一次获取所有符合条件的数据,然后在内存中对大数据进行操作即可实现分页效果。
本教程操作环境:windows7系统、java8、dell g3电脑。
mybatis的分页方式可分为大的两类:
1、逻辑分页
2、物理分页
mybatis分页的具体方式
借助数组进行分页(逻辑分页)
借助sql语句进行分页(物理分页)
拦截器分页 (物理分页) 通过拦截器给sql语句末尾加上limit语句来查询,一劳永逸最优
rowbounds实现分页 (逻辑分页)
1、借助数组进行分页
原理:进行数据库查询操作时,获取到数据库中所有满足条件的记录,保存在应用的临时数组中,再通过list的sublist方法,获取到满足条件的所有记录。
实现:
首先在dao层,创建studentmapper接口,用于对数据库的操作。在接口中定义通过数组分页的查询方法,如下所示:
listquerystudentsbyarray();
方法很简单,就是获取所有的数据,通过list接收后进行分页操作。
创建studentmapper.xml文件,编写查询的sql语句:
可以看出再编写sql语句的时候,我们并没有作任何分页的相关操作。这里是查询到所有的学生信息。
接下来在service层获取数据并且进行分页实现:
定义istuservice接口,并且定义分页方法:
listquerystudentsbyarray(int currpage, int pagesize);
通过接收currpage参数表示显示第几页的数据,pagesize表示每页显示的数据条数。
创建istuservice接口实现类stuserviceiml对方法进行实现,对获取到的数组通过currpage和pagesize进行分页:
@override public listquerystudentsbyarray(int currpage, int pagesize) { list 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 listgetstudentbyarray(@pathvariable("currpage") int currpage, @pathvariable("pagesize") int pagesize) { list student = stuserviceiml.querystudentsbyarray(currpage, pagesize); return student; }
通过用户传入的currpage和pagesize获取指定数据。
测试:
首先我们来获取再没实现分页效果前获取到的所有数据,如下所示:
接下来在浏览器输入http://localhost:8080/student/student/array/1/2
测试实现了分页后的数据。获取第一页的数据,每页显示两条数据。
结果如下:
输出的是指定的从第0-2条数据,可见我们通过数组分页的功能是成功的。(这里因为用到了关联查询,所以看起来数据可能比较多)
缺点:数据库查询并返回所有的数据,而我们需要的只是极少数符合要求的数据。当数据量少时,还可以接受。当数据库数据量过大时,每次查询对数据库和程序的性能都会产生极大的影响。
2、借助sql语句进行分页
在了解到通过数组分页的缺陷后,我们发现不能每次都对数据库中的所有数据都检索。然后在程序中对获取到的大量数据进行二次操作,这样对空间和性能都是极大的损耗。所以我们希望能直接在数据库语言中只检索符合条件的记录,不需要在通过程序对其作处理。这时,sql语句分页技术横空出世。
实现:通过sql语句实现分页也是非常简单的,只是需要改变我们查询的语句就能实现了,即在sql语句后面添加limit分页语句。
首先还是在studentmapper接口中添加sql语句查询的方法,如下:
listquerystudentsbysql(map data);
然后在studentmapper.xml文件中编写sql语句通过limiy关键字进行分页:
接下来还是在istuservice接口中定义方法,并且在stuserviceiml中对sql分页实现。
listquerystudentsbysql(int currpage, int pagesize);
@override public listquerystudentsbysql(int currpage, int pagesize) { map data = new hashedmap(); data.put("currindex", (currpage-1)*pagesize); data.put("pagesize", pagesize); return studentmapper.querystudentsbysql(data); }
sql分页语句如下:select * from table limit index, pagesize;
所以在service中计算出currindex:要开始查询的第一条记录的索引。
测试:
在浏览器输入http://localhost:8080/student/student/sql/1/2
获取第一页的数据,每页显示两条数据。
结果:
从输出结果可以看出和数组分页的结果是一致的,因此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; /** * created by chenboge on 2017/5/7. ** email:baigegechen@gmail.com *
* description: */ /** * @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
paraobject = (map ) parameterhandler.getparameterobject(); //也可以这样获取 //paraobject = (map ) 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"); } }
上面即是拦截器功能的实现,在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利用一些技术(内部反射实现)很轻松的读取或修改里面的数据。
接下来说说: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"); //获取请求时的参数 mapparaobject = (map ) parameterhandler.getparameterobject(); //也可以这样获取 //paraobject = (map ) statementhandler.getboundsql().getparameterobject();
拿到我们需要的currpage和pagesize参数后,就是组装分页查询的sql语句’limitsql‘了。
最后通过metaobjecthandler.setvalue("delegate.boundsql.sql", limitsql);
将原始的sql语句替换成我们新的分页语句,完成偷天换日的功能,接下来让代码继续执行。
编写好拦截器后,需要注册到项目中,才能发挥它的作用。在mybatis的配置文件中,添加如下代码:
如上所示,还能在里面配置一些属性,在拦截器的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结尾的请求,所以在这里,我们的方法名称也以此结尾:
方法 listquerystudentsbypage(map data); xml文件的select语句
可以看出,这里我们就不需要再去手动配置分页语句了。
接下来是service层的接口编写和实现方法:
方法: listquerystudentsbypage(int currpage,int pagesize); 实现: @override public list querystudentsbypage(int currpage, int pagesize) { map 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 listgetstudentbypage(@pathvariable("currpage") int currpage, @pathvariable("pagesize") int pagesize) { list student = stuserviceiml.querystudentsbypage(currpage, pagesize); return student; }
测试:
在浏览器输入:http://localhost:8080/student/student/page/1/2
结果:
可见和上面两种分页的效果是一样的。
4、rowbounds实现分页
原理:通过rowbounds实现分页和通过数组方式分页原理差不多,都是一次获取所有符合条件的数据,然后在内存中对大数据进行操作,实现分页效果。只是数组分页需要我们自己去实现分页逻辑,这里更加简化而已。
存在问题:一次性从数据库获取的数据可能会很多,对内存的消耗很大,可能导师性能变差,甚至引发内存溢出。
适用场景:在数据量很大的情况下,建议还是适用拦截器实现分页效果。rowbounds建议在数据量相对较小的情况下使用。
简单介绍:这是代码实现上最简单的一种分页方式,只需要在dao层接口中要实现分页的方法中加入rowbounds参数,然后在service层通过offset(从第几行开始读取数据,默认值为0)和limit(要显示的记录条数,默认为java允许的最大整数:2147483647)两个参数构建出rowbounds对象,在调用dao层方法的时,将构造好的rowbounds传进去就能轻松实现分页效果了。
具体操作如下:
dao层接口方法:
//加入rowbounds参数 public listqueryusersbypage(string username, rowbounds rowbounds);
然后在service层构建rowbounds,调用dao层方法:
@override @transactional(isolation = isolation.read_committed, propagation = propagation.supports) public listqueryrolesbypage(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分页导致的性能问题。当然,具体情况可以采取不同的百家乐凯发k8的解决方案。数据量小时,rowbounds不失为一种好办法。但是数据量大时,实现拦截器就很有必要了。
【相关推荐:编程教学】
以上就是mybatis分页的几种方式的详细内容,更多请关注其它相关文章!